ソースを参照

!28 语音对讲合并
语音对讲合并

panll 1 年間 前
コミット
b86f0aaae2
81 ファイル変更7312 行追加3865 行削除
  1. 8 0
      README.md
  2. 2 2
      doc/README.md
  3. 27 0
      doc/_content/broadcast.md
  4. 3 3
      doc/_content/introduction/deployment.md
  5. 46 0
      doc/_content/theory/broadcast_cascade.md
  6. 1 0
      doc/_sidebar.md
  7. 3 1
      src/main/java/com/genersoft/iot/vmp/common/InviteSessionType.java
  8. 2 2
      src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java
  9. 1 0
      src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java
  10. 4 4
      src/main/java/com/genersoft/iot/vmp/conf/GlobalResponseAdvice.java
  11. 1 1
      src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java
  12. 11 1
      src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java
  13. 0 0
      src/main/java/com/genersoft/iot/vmp/conf/redis/RedisConfig.java
  14. 3 0
      src/main/java/com/genersoft/iot/vmp/conf/security/dto/LoginUser.java
  15. 159 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/AudioBroadcastCatch.java
  16. 15 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/AudioBroadcastCatchStatus.java
  17. 8 2
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java
  18. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/InviteStreamType.java
  19. 3 3
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java
  20. 18 5
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpItem.java
  21. 20 1
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/SipTransactionInfo.java
  22. 5 3
      src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java
  23. 103 0
      src/main/java/com/genersoft/iot/vmp/gb28181/session/AudioBroadcastManager.java
  24. 2 2
      src/main/java/com/genersoft/iot/vmp/gb28181/task/SipRunner.java
  25. 15 13
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
  26. 48 14
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java
  27. 81 4
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java
  28. 387 320
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java
  29. 1473 1424
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
  30. 131 6
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
  31. 10 2
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorParent.java
  32. 82 70
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
  33. 55 6
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java
  34. 241 70
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
  35. 3 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java
  36. 3 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java
  37. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/info/InfoRequestProcessor.java
  38. 17 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageHandlerAbstract.java
  39. 197 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/BroadcastNotifyMessageHandler.java
  40. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java
  41. 41 17
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/BroadcastResponseMessageHandler.java
  42. 1 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java
  43. 3 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java
  44. 20 0
      src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java
  45. 2 2
      src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java
  46. 906 820
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
  47. 9 2
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java
  48. 58 9
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerFactory.java
  49. 2 0
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZlmHttpHookSubscribe.java
  50. 16 0
      src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeFactory.java
  51. 43 0
      src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeForStreamPush.java
  52. 168 0
      src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaServerItemLite.java
  53. 4 2
      src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java
  54. 1 1
      src/main/java/com/genersoft/iot/vmp/service/IMediaService.java
  55. 25 0
      src/main/java/com/genersoft/iot/vmp/service/IPlatformService.java
  56. 28 0
      src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
  57. 1 1
      src/main/java/com/genersoft/iot/vmp/service/bean/RequestSendItemMsg.java
  58. 30 4
      src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java
  59. 12 3
      src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
  60. 6 10
      src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java
  61. 1 1
      src/main/java/com/genersoft/iot/vmp/service/impl/PlatformChannelServiceImpl.java
  62. 356 11
      src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java
  63. 553 14
      src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
  64. 0 6
      src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java
  65. 4 4
      src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamCloseResponseListener.java
  66. 9 0
      src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java
  67. 1 1
      src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
  68. 0 6
      src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java
  69. 59 0
      src/main/java/com/genersoft/iot/vmp/vmanager/bean/AudioBroadcastResult.java
  70. 37 58
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java
  71. 9 0
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/bean/AudioBroadcastEvent.java
  72. 1 1
      src/main/java/com/genersoft/iot/vmp/vmanager/ps/PsController.java
  73. 2 2
      src/main/java/com/genersoft/iot/vmp/vmanager/rtp/RtpController.java
  74. 14 0
      src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java
  75. 4 0
      src/main/resources/all-application.yml
  76. BIN
      src/main/resources/local.jks
  77. 2 2
      web_src/config/index.js
  78. 4 0
      web_src/src/components/dialog/deviceEdit.vue
  79. 857 598
      web_src/src/components/dialog/devicePlayer.vue
  80. 831 327
      web_src/static/js/ZLMRTCClient.js
  81. 1 1
      web_src/static/js/ZLMRTCClient.js.map

+ 8 - 0
README.md

@@ -134,3 +134,11 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git
 [mk1990](https://github.com/mk1990) [SaltFish001](https://github.com/SaltFish001)
 
 
+ffmpeg -re -i 123.mp3 -acodec pcm_alaw -ar 8000 -ac 1  -f rtsp rtsp://192.168.1.3:30554/broadcast/34020000001320000101_34020000001310000001
+
+ffmpeg -re -i 123.mp3 -acodec pcm_alaw -ar 8000 -ac 1  -f rtsp rtsp://192.168.1.3:30554/talk/34020000001320000011_34020000001370000001
+
+
+
+ffmpeg -re -i 123.mp3 -acodec pcm_alaw -ar 8000 -ac 1  -f rtsp rtsp://192.168.1.3:30554/talk/34020000001320000101_34020000001310000001
+

+ 2 - 2
doc/README.md

@@ -56,7 +56,7 @@
     - [X] 报警订阅
     - [X] 目录订阅
 - [ ] 语音广播
-- [ ] 语音对讲
+- [ ] 语音喊话
 
 **作为下级平台**
 - [X] 注册
@@ -95,7 +95,7 @@
     - [ ] 报警订阅
     - [X] 目录订阅
 - [ ] 语音广播
-- [ ] 语音对讲
+- [ ] 语音喊话
 
    
 

+ 27 - 0
doc/_content/broadcast.md

@@ -0,0 +1,27 @@
+# 原理图
+
+## 使用ffmpeg测试语音对讲原理
+```plantuml
+@startuml
+"FFMPEG" -> "ZLMediaKit": 推流到zlm
+"WVP-PRO" <- "ZLMediaKit": 通知收到语音对讲推流,携带设备和通道信息
+"WVP-PRO" -> "设备": 开始语音对讲
+"WVP-PRO" <-- "设备": 语音对讲建立成功,携带收流端口
+"WVP-PRO" -> "ZLMediaKit": 通知zlm将流推送到设备收流端口
+"ZLMediaKit" -> "设备": 向设备推流
+@enduml
+```
+
+## 使用网页测试语音对讲原理
+```plantuml
+@startuml
+"前端页面" -> "WVP-PRO": 请求推流地址
+"前端页面" <-- "WVP-PRO": 返回推流地址
+"前端页面" -> "ZLMediaKit": 使用webrtc推流到zlm,以下过程相同
+"WVP-PRO" <- "ZLMediaKit": 通知收到语音对讲推流,携带设备和通道信息
+"WVP-PRO" -> "设备": 开始语音对讲
+"WVP-PRO" <-- "设备": 语音对讲建立成功,携带收流端口
+"WVP-PRO" -> "ZLMediaKit": 通知zlm将流推送到设备收流端口
+"ZLMediaKit" -> "设备": 向设备推流
+@enduml
+```

+ 3 - 3
doc/_content/introduction/deployment.md

@@ -21,9 +21,9 @@
 4. WVP-PRO与ZLM支持分开部署,但是wvp-pro-assist必须与zlm部署在同一台主机;
 5. 生产环境按需开放端口,但是建议修改默认端口,尤其是5060端口,易受到攻击;
 6. zlm使用docker部署的情况,要求端口映射一致,比如映射5060,应将外部端口也映射为5060端口;
-7. 启动服务,以linux为例  
-### 启动WVP-PRO  
-**jar包:**
+7. zlm与wvp会保持高频率的通信,所以不要去将wvp与zlm分属在两个网络,比如wvp在内网,zlm却在公网的情况。
+8. 启动服务,以linux为例
+**启动WVP-PRO**
 ```shell
 nohup java -jar wvp-pro-*.jar &
 ```

+ 46 - 0
doc/_content/theory/broadcast_cascade.md

@@ -0,0 +1,46 @@
+<!-- 点播流程 -->
+
+# 点播流程
+> 以下为WVP-PRO级联语音喊话流程。
+
+```plantuml
+@startuml
+"上级平台"  -> "下级平台": 1. 发起语音喊话请求
+"上级平台" <--  "下级平台": 2. 200OK
+"上级平台" <- "下级平台": 3. 回复Result OK
+"上级平台" -->  "下级平台": 4. 200OK
+
+"下级平台"  -> "设备": 5. 发起语音喊话请求
+"下级平台" <--  "设备": 6. 200OK
+"下级平台" <- "设备": 7. 回复Result OK
+"下级平台" -->  "设备": 8. 200OK
+
+"下级平台"  <- "设备": 9. invite(broadcast)
+"下级平台"  --> "设备": 10. 100 trying
+"下级平台"  --> "设备": 11. 200OK SDP
+"下级平台"  <-- "设备": 12. ack
+
+"上级平台"  <- "下级平台": 13. invite(broadcast)
+"上级平台"  --> "下级平台": 14. 100 trying
+"上级平台"  --> "下级平台": 15. 200OK SDP
+"上级平台"  <-- "下级平台": 16. ack
+
+"上级平台"  -> "下级平台": 17. 推送RTP
+"下级平台"  -> "设备": 18. 推送RTP
+
+@enduml
+```
+
+
+##  注册流程描述如下:
+1. 用户从网页或调用接口发起点播请求;
+2. WVP-PRO向摄像机发送Invite消息,消息头域中携带 Subject字段,表明点播的视频源ID、发送方媒体流序列号、ZLMediaKit接收流使用的IP、端口号、
+   接收端媒体流序列号等参数,SDP消息体中 s字段为“Play”代表实时点播,y字段描述SSRC值,f字段描述媒体参数。
+3. 摄像机向WVP-PRO回复200OK,消息体中描述了媒体流发送者发送媒体流的IP、端口、媒体格式、SSRC字段等内容。
+4. WVP-PRO向设备回复Ack, 会话建立成功。
+5. 设备向ZLMediaKit发送实时流。
+6. ZLMediaKit向WVP-PRO发送流改变事件。
+7. WVP-PRO向WEB用户回复播放地址。
+8. ZLMediaKit向WVP发送流无人观看事件。
+9. WVP-PRO向设备回复Bye, 结束会话。
+10. 设备回复200OK,会话结束成功。

+ 1 - 0
doc/_sidebar.md

@@ -20,6 +20,7 @@
   * [树形结构](_content/theory/channel_tree.md)
   * [注册流程](_content/theory/register.md)
   * [点播流程](_content/theory/play.md)
+  * [级联语音喊话流程](_content/theory/broadcast_cascade.md)
 * **必备技巧**
   * [抓包](_content/skill/tcpdump.md)
 

+ 3 - 1
src/main/java/com/genersoft/iot/vmp/common/InviteSessionType.java

@@ -3,5 +3,7 @@ package com.genersoft.iot.vmp.common;
 public enum InviteSessionType {
     PLAY,
     PLAYBACK,
-    DOWNLOAD
+    DOWNLOAD,
+    BROADCAST,
+    TALK
 }

+ 2 - 2
src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java

@@ -240,11 +240,11 @@ public class StreamInfo implements Serializable, Cloneable{
         }
     }
 
-    public void setRtc(String host, int port, int sslPort, String app, String stream, String callIdParam) {
+    public void setRtc(String host, int port, int sslPort, String app, String stream, String callIdParam, boolean isPlay) {
         if (callIdParam != null) {
             callIdParam = Objects.equals(callIdParam, "") ? callIdParam : callIdParam.replace("?", "&");
         }
-        String file = String.format("index/api/webrtc?app=%s&stream=%s&type=play%s", app, stream, callIdParam);
+        String file = String.format("index/api/webrtc?app=%s&stream=%s&type=%s%s", app, stream, isPlay?"play":"push", callIdParam);
         if (port > 0) {
             this.rtc = new StreamURL("http", host, port, file);
         }

+ 1 - 0
src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java

@@ -68,6 +68,7 @@ public class VideoManagerConstants {
 	public static final String SYSTEM_INFO_NET_PREFIX = "VMP_SYSTEM_INFO_NET_";
 
 	public static final String SYSTEM_INFO_DISK_PREFIX = "VMP_SYSTEM_INFO_DISK_";
+	public static final String BROADCAST_WAITE_INVITE = "task_broadcast_waite_invite_";
 
 	public static final String REGISTER_EXPIRE_TASK_KEY_PREFIX = "VMP_device_register_expire_";
 	public static final String PUSH_STREAM_LIST = "VMP_PUSH_STREAM_LIST_";

+ 4 - 4
src/main/java/com/genersoft/iot/vmp/conf/GlobalResponseAdvice.java

@@ -32,7 +32,7 @@ public class GlobalResponseAdvice implements ResponseBodyAdvice<Object> {
     @Override
     public Object beforeBodyWrite(Object body, @NotNull MethodParameter returnType, @NotNull MediaType selectedContentType, @NotNull Class<? extends HttpMessageConverter<?>> selectedConverterType, @NotNull ServerHttpRequest request, @NotNull ServerHttpResponse response) {
         // 排除api文档的接口,这个接口不需要统一
-        String[] excludePath = {"/v3/api-docs","/api/v1","/index/hook"};
+        String[] excludePath = {"/v3/api-docs","/api/v1","/index/hook","/api/video-"};
         for (String path : excludePath) {
             if (request.getURI().getPath().startsWith(path)) {
                 return body;
@@ -59,8 +59,8 @@ public class GlobalResponseAdvice implements ResponseBodyAdvice<Object> {
      * 防止返回string时出错
      * @return
      */
-    @Bean
-    public HttpMessageConverters fast() {
+    /*@Bean
+    public HttpMessageConverters custHttpMessageConverter() {
         return new HttpMessageConverters(new FastJsonHttpMessageConverter());
-    }
+    }*/
 }

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java

@@ -97,7 +97,7 @@ public class MediaConfig{
 
     public String getHookIp() {
         if (ObjectUtils.isEmpty(hookIp)){
-            return sipIp.split(",")[0];
+            return sipIp;
         }else {
             return hookIp;
         }

+ 11 - 1
src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java

@@ -33,7 +33,7 @@ public class UserSetting {
 
     private Boolean logInDatabase = Boolean.TRUE;
 
-    private Boolean usePushingAsStatus = Boolean.TRUE;
+    private Boolean usePushingAsStatus = Boolean.FALSE;
 
     private Boolean useSourceIpAsStreamIp = Boolean.FALSE;
 
@@ -58,6 +58,8 @@ public class UserSetting {
 
     private String thirdPartyGBIdReg = "[\\s\\S]*";
 
+    private String broadcastForPlatform = "UDP";
+
     private String civilCodeFile = "classpath:civilCode.csv";
 
     private List<String> interfaceAuthenticationExcludes = new ArrayList<>();
@@ -210,6 +212,14 @@ public class UserSetting {
         this.syncChannelOnDeviceOnline = syncChannelOnDeviceOnline;
     }
 
+    public String getBroadcastForPlatform() {
+        return broadcastForPlatform;
+    }
+
+    public void setBroadcastForPlatform(String broadcastForPlatform) {
+        this.broadcastForPlatform = broadcastForPlatform;
+    }
+
     public Boolean getSipUseSourceIpAsRemoteAddress() {
         return sipUseSourceIpAsRemoteAddress;
     }

+ 0 - 0
src/main/java/com/genersoft/iot/vmp/conf/redis/RedisConfig.java


+ 3 - 0
src/main/java/com/genersoft/iot/vmp/conf/security/dto/LoginUser.java

@@ -100,6 +100,9 @@ public class LoginUser implements UserDetails, CredentialsContainer {
         return user.getRole();
     }
 
+    public String getPushKey() {
+        return user.getPushKey();
+    }
 
     public String getAccessToken() {
         return accessToken;

+ 159 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/AudioBroadcastCatch.java

@@ -0,0 +1,159 @@
+package com.genersoft.iot.vmp.gb28181.bean;
+
+
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.AudioBroadcastEvent;
+import gov.nist.javax.sip.message.SIPResponse;
+
+/**
+ * 缓存语音广播的状态
+ * @author lin
+ */
+public class AudioBroadcastCatch {
+
+
+    public AudioBroadcastCatch(
+            String deviceId,
+            String channelId,
+            MediaServerItem mediaServerItem,
+            String app,
+            String stream,
+            AudioBroadcastEvent event,
+            AudioBroadcastCatchStatus status,
+            boolean isFromPlatform
+    ) {
+        this.deviceId = deviceId;
+        this.channelId = channelId;
+        this.status = status;
+        this.event = event;
+        this.isFromPlatform = isFromPlatform;
+        this.app = app;
+        this.stream = stream;
+        this.mediaServerItem = mediaServerItem;
+    }
+
+    public AudioBroadcastCatch() {
+    }
+
+    /**
+     * 设备编号
+     */
+    private String deviceId;
+
+    /**
+     * 通道编号
+     */
+    private String channelId;
+
+    /**
+     * 流媒体信息
+     */
+    private MediaServerItem mediaServerItem;
+
+    /**
+     * 关联的流APP
+     */
+    private String app;
+
+    /**
+     * 关联的流STREAM
+     */
+    private String stream;
+
+    /**
+     *  是否是级联语音喊话
+     */
+    private boolean isFromPlatform;
+
+    /**
+     * 语音广播状态
+     */
+    private AudioBroadcastCatchStatus status;
+
+    /**
+     * 请求信息
+     */
+    private SipTransactionInfo sipTransactionInfo;
+
+    /**
+     * 请求结果回调
+     */
+    private AudioBroadcastEvent event;
+
+
+    public String getDeviceId() {
+        return deviceId;
+    }
+
+    public void setDeviceId(String deviceId) {
+        this.deviceId = deviceId;
+    }
+
+    public String getChannelId() {
+        return channelId;
+    }
+
+    public void setChannelId(String channelId) {
+        this.channelId = channelId;
+    }
+
+    public AudioBroadcastCatchStatus getStatus() {
+        return status;
+    }
+
+    public void setStatus(AudioBroadcastCatchStatus status) {
+        this.status = status;
+    }
+
+    public SipTransactionInfo getSipTransactionInfo() {
+        return sipTransactionInfo;
+    }
+
+    public MediaServerItem getMediaServerItem() {
+        return mediaServerItem;
+    }
+
+    public void setMediaServerItem(MediaServerItem mediaServerItem) {
+        this.mediaServerItem = mediaServerItem;
+    }
+
+    public String getApp() {
+        return app;
+    }
+
+    public void setApp(String app) {
+        this.app = app;
+    }
+
+    public String getStream() {
+        return stream;
+    }
+
+    public void setStream(String stream) {
+        this.stream = stream;
+    }
+
+    public boolean isFromPlatform() {
+        return isFromPlatform;
+    }
+
+    public void setFromPlatform(boolean fromPlatform) {
+        isFromPlatform = fromPlatform;
+    }
+
+    public void setSipTransactionInfo(SipTransactionInfo sipTransactionInfo) {
+        this.sipTransactionInfo = sipTransactionInfo;
+    }
+
+    public AudioBroadcastEvent getEvent() {
+        return event;
+    }
+
+    public void setEvent(AudioBroadcastEvent event) {
+        this.event = event;
+    }
+
+    public void setSipTransactionInfoByRequset(SIPResponse sipResponse) {
+        this.sipTransactionInfo = new SipTransactionInfo(sipResponse);
+    }
+}

+ 15 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/AudioBroadcastCatchStatus.java

@@ -0,0 +1,15 @@
+package com.genersoft.iot.vmp.gb28181.bean;
+
+/**
+ * 语音广播状态
+ * @author lin
+ */
+public enum AudioBroadcastCatchStatus {
+
+    // 发送语音广播消息等待对方回复语音广播
+    Ready,
+    // 收到回复等待invite消息
+    WaiteInvite,
+    // 收到invite消息
+    Ok,
+}

+ 8 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java

@@ -188,8 +188,8 @@ public class Device {
 	@Schema(description = "设备注册的事务信息")
 	private SipTransactionInfo sipTransactionInfo;
 
-
-
+	@Schema(description = "控制语音对讲流程,释放收到ACK后发流")
+	private boolean broadcastPushAfterAck;
 
 	public String getDeviceId() {
 		return deviceId;
@@ -452,5 +452,11 @@ public class Device {
 	public void setSipTransactionInfo(SipTransactionInfo sipTransactionInfo) {
 		this.sipTransactionInfo = sipTransactionInfo;
 	}
+    public boolean isBroadcastPushAfterAck() {
+        return broadcastPushAfterAck;
+    }
 
+    public void setBroadcastPushAfterAck(boolean broadcastPushAfterAck) {
+        this.broadcastPushAfterAck = broadcastPushAfterAck;
+    }
 }

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/bean/InviteStreamType.java

@@ -2,7 +2,7 @@ package com.genersoft.iot.vmp.gb28181.bean;
 
 public enum InviteStreamType {
 
-    PLAY,PLAYBACK,DOWNLOAD,PUSH,PROXY,CLOUD_RECORD_PUSH,CLOUD_RECORD_PROXY
+    PLAY,PLAYBACK,DOWNLOAD,PUSH,PROXY,CLOUD_RECORD_PUSH,CLOUD_RECORD_PROXY,BROADCAST,TALK
 
 
 }

+ 3 - 3
src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java

@@ -66,7 +66,7 @@ public class ParentPlatform {
      * 设备端口
      */
     @Schema(description = "设备端口")
-    private String devicePort;
+    private int devicePort;
 
     /**
      * SIP认证用户名(默认使用设备国标编号)
@@ -261,11 +261,11 @@ public class ParentPlatform {
         this.deviceIp = deviceIp;
     }
 
-    public String getDevicePort() {
+    public int getDevicePort() {
         return devicePort;
     }
 
-    public void setDevicePort(String devicePort) {
+    public void setDevicePort(int devicePort) {
         this.devicePort = devicePort;
     }
 

+ 18 - 5
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpItem.java

@@ -49,7 +49,7 @@ public class SendRtpItem {
     /**
      * 设备推流的streamId
      */
-    private String streamId;
+    private String stream;
 
     /**
      * 是否为tcp
@@ -117,6 +117,11 @@ public class SendRtpItem {
      */
     private InviteStreamType playType;
 
+    /**
+     * 发流的同时收流
+     */
+    private String receiveStream;
+
     public String getIp() {
         return ip;
     }
@@ -181,12 +186,12 @@ public class SendRtpItem {
         this.app = app;
     }
 
-    public String getStreamId() {
-        return streamId;
+    public String getStream() {
+        return stream;
     }
 
-    public void setStreamId(String streamId) {
-        this.streamId = streamId;
+    public void setStream(String stream) {
+        this.stream = stream;
     }
 
     public boolean isTcp() {
@@ -292,4 +297,12 @@ public class SendRtpItem {
     public void setRtcp(boolean rtcp) {
         this.rtcp = rtcp;
     }
+
+    public String getReceiveStream() {
+        return receiveStream;
+    }
+
+    public void setReceiveStream(String receiveStream) {
+        this.receiveStream = receiveStream;
+    }
 }

+ 20 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SipTransactionInfo.java

@@ -1,6 +1,5 @@
 package com.genersoft.iot.vmp.gb28181.bean;
 
-import gov.nist.javax.sip.message.SIPRequest;
 import gov.nist.javax.sip.message.SIPResponse;
 
 public class SipTransactionInfo {
@@ -10,11 +9,23 @@ public class SipTransactionInfo {
     private String toTag;
     private String viaBranch;
 
+    // 自己是否媒体流发送者
+    private boolean asSender;
+
+    public SipTransactionInfo(SIPResponse response, boolean asSender) {
+        this.callId = response.getCallIdHeader().getCallId();
+        this.fromTag = response.getFromTag();
+        this.toTag = response.getToTag();
+        this.viaBranch = response.getTopmostViaHeader().getBranch();
+        this.asSender = asSender;
+    }
+
     public SipTransactionInfo(SIPResponse response) {
         this.callId = response.getCallIdHeader().getCallId();
         this.fromTag = response.getFromTag();
         this.toTag = response.getToTag();
         this.viaBranch = response.getTopmostViaHeader().getBranch();
+        this.asSender = false;
     }
 
     public SipTransactionInfo() {
@@ -51,4 +62,12 @@ public class SipTransactionInfo {
     public void setViaBranch(String viaBranch) {
         this.viaBranch = viaBranch;
     }
+
+    public boolean isAsSender() {
+        return asSender;
+    }
+
+    public void setAsSender(boolean asSender) {
+        this.asSender = asSender;
+    }
 }

+ 5 - 3
src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java

@@ -61,7 +61,7 @@ public class SipSubscribe {
         logger.debug("errorSubscribes.size:{}",errorSubscribes.size());
     }
 
-    public interface Event { void response(EventResult eventResult) ;
+    public interface Event { void response(EventResult eventResult);
     }
 
     /**
@@ -78,8 +78,10 @@ public class SipSubscribe {
         dialogTerminated,
         // 设备未找到
         deviceNotFoundEvent,
-        // 设备未找到
-        cmdSendFailEvent
+        // 消息发送失败
+        cmdSendFailEvent,
+        // 消息发送失败
+        failedToGetPort
     }
 
     public static class EventResult<EventObject>{

+ 103 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/session/AudioBroadcastManager.java

@@ -0,0 +1,103 @@
+package com.genersoft.iot.vmp.gb28181.session;
+
+import com.genersoft.iot.vmp.conf.SipConfig;
+import com.genersoft.iot.vmp.gb28181.bean.AudioBroadcastCatch;
+import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 语音广播消息管理类
+ * @author lin
+ */
+@Component
+public class AudioBroadcastManager {
+
+    @Autowired
+    private SipConfig config;
+
+    public static Map<String, AudioBroadcastCatch> data = new ConcurrentHashMap<>();
+
+    public void update(AudioBroadcastCatch audioBroadcastCatch) {
+        if (SipUtils.isFrontEnd(audioBroadcastCatch.getDeviceId())) {
+            data.put(audioBroadcastCatch.getDeviceId(), audioBroadcastCatch);
+        }else {
+            data.put(audioBroadcastCatch.getDeviceId() + audioBroadcastCatch.getChannelId(), audioBroadcastCatch);
+        }
+    }
+
+    public void del(String deviceId, String channelId) {
+        if (SipUtils.isFrontEnd(deviceId)) {
+            data.remove(deviceId);
+        }else {
+            data.remove(deviceId + channelId);
+        }
+
+    }
+
+    public void delByDeviceId(String deviceId) {
+        for (String key : data.keySet()) {
+            if (key.startsWith(deviceId)) {
+                data.remove(key);
+            }
+        }
+    }
+
+    public List<AudioBroadcastCatch> getAll(){
+        Collection<AudioBroadcastCatch> values = data.values();
+        return new ArrayList<>(values);
+    }
+
+
+    public boolean exit(String deviceId, String channelId) {
+        for (String key : data.keySet()) {
+            if (SipUtils.isFrontEnd(deviceId)) {
+                return key.equals(deviceId);
+            }else {
+                return key.equals(deviceId + channelId);
+            }
+        }
+        return false;
+    }
+
+    public AudioBroadcastCatch get(String deviceId, String channelId) {
+        AudioBroadcastCatch audioBroadcastCatch;
+        if (SipUtils.isFrontEnd(deviceId)) {
+            audioBroadcastCatch = data.get(deviceId);
+        }else {
+            audioBroadcastCatch = data.get(deviceId + channelId);
+        }
+        if (audioBroadcastCatch == null) {
+            Stream<AudioBroadcastCatch> allAudioBroadcastCatchStreamForDevice = data.values().stream().filter(
+                    audioBroadcastCatchItem -> Objects.equals(audioBroadcastCatchItem.getDeviceId(), deviceId));
+            List<AudioBroadcastCatch> audioBroadcastCatchList = allAudioBroadcastCatchStreamForDevice.collect(Collectors.toList());
+            if (audioBroadcastCatchList.size() == 1 && Objects.equals(config.getId(), channelId)) {
+                audioBroadcastCatch = audioBroadcastCatchList.get(0);
+            }
+        }
+
+        return audioBroadcastCatch;
+    }
+
+    public List<AudioBroadcastCatch> get(String deviceId) {
+        List<AudioBroadcastCatch> audioBroadcastCatchList= new ArrayList<>();
+        if (SipUtils.isFrontEnd(deviceId)) {
+            if (data.get(deviceId) != null) {
+                audioBroadcastCatchList.add(data.get(deviceId));
+            }
+        }else {
+            for (String key : data.keySet()) {
+                if (key.startsWith(deviceId)) {
+                    audioBroadcastCatchList.add(data.get(key));
+                }
+            }
+        }
+
+        return audioBroadcastCatchList;
+    }
+}

+ 2 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/task/SipRunner.java

@@ -106,13 +106,13 @@ public class SipRunner implements CommandLineRunner {
         if (sendRtpItems.size() > 0) {
             for (SendRtpItem sendRtpItem : sendRtpItems) {
                 MediaServerItem mediaServerItem = mediaServerService.getOne(sendRtpItem.getMediaServerId());
-                redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(),sendRtpItem.getChannelId(), sendRtpItem.getCallId(),sendRtpItem.getStreamId());
+                redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(),sendRtpItem.getChannelId(), sendRtpItem.getCallId(),sendRtpItem.getStream());
                 if (mediaServerItem != null) {
                     ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc());
                     Map<String, Object> param = new HashMap<>();
                     param.put("vhost","__defaultVhost__");
                     param.put("app",sendRtpItem.getApp());
-                    param.put("stream",sendRtpItem.getStreamId());
+                    param.put("stream",sendRtpItem.getStream());
                     param.put("ssrc",sendRtpItem.getSsrc());
                     JSONObject jsonObject = zlmresTfulUtils.stopSendRtp(mediaServerItem, param);
                     if (jsonObject != null && jsonObject.getInteger("code") == 0) {

+ 15 - 13
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java

@@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd;
 
 import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
+import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
 import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
@@ -124,13 +125,19 @@ public interface ISIPCommander {
 						   String startTime, String endTime, int downloadSpeed, ZlmHttpHookSubscribe.Event hookEvent,
 						   SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
 
+
 	/**
 	 * 视频流停止
 	 */
 	void streamByeCmd(Device device, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException;
 
+	void talkStreamCmd(MediaServerItem mediaServerItem, SendRtpItem sendRtpItem, Device device, String channelId, String callId, ZlmHttpHookSubscribe.Event event, ZlmHttpHookSubscribe.Event eventForPush, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+
+
 	void streamByeCmd(Device device, String channelId, String stream, String callId) throws InvalidArgumentException, ParseException, SipException, SsrcTransactionNotFoundException;
 
+	void streamByeCmd(Device device, String channelId, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException;
+
 	/**
 	 * 回放暂停
 	 */
@@ -160,22 +167,16 @@ public interface ISIPCommander {
 	void playbackControlCmd(Device device, StreamInfo streamInfo, String content,SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException;
 
 
+    void streamByeCmdForDeviceInvite(Device device, String channelId, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException;
+
     /**
+	 * /**
 	 * 语音广播
-	 * 
-	 * @param device  视频设备
-	 * @param channelId  预览通道
-	 */
-	void audioBroadcastCmd(Device device,String channelId);
-	
-	/**
-	 * 语音广播
-	 * 
-	 * @param device  视频设备
+	 *
+	 * @param device 视频设备
 	 */
-	void audioBroadcastCmd(Device device, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
-	void audioBroadcastCmd(Device device) throws InvalidArgumentException, SipException, ParseException;
-	
+	void audioBroadcastCmd(Device device, String channelId, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+
 	/**
 	 * 音视频录像控制
 	 * 
@@ -362,4 +363,5 @@ public interface ISIPCommander {
 	 */
 	void sendAlarmMessage(Device device, DeviceAlarm deviceAlarm) throws InvalidArgumentException, SipException, ParseException;
 
+
 }

+ 48 - 14
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java

@@ -1,8 +1,12 @@
 package com.genersoft.iot.vmp.gb28181.transmit.cmd;
 
+import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
+import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
+import com.genersoft.iot.vmp.service.bean.SSRCInfo;
 
 import javax.sip.InvalidArgumentException;
 import javax.sip.SipException;
@@ -14,6 +18,7 @@ public interface ISIPCommanderForPlatform {
 
     /**
      * 向上级平台注册
+     *
      * @param parentPlatform
      * @return
      */
@@ -26,6 +31,7 @@ public interface ISIPCommanderForPlatform {
 
     /**
      * 向上级平台注销
+     *
      * @param parentPlatform
      * @return
      */
@@ -34,26 +40,33 @@ public interface ISIPCommanderForPlatform {
 
     /**
      * 向上级平发送心跳信息
+     *
      * @param parentPlatform
      * @return callId(作为接受回复的判定)
      */
-    String keepalive(ParentPlatform parentPlatform,SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException;
+    String keepalive(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent)
+            throws SipException, InvalidArgumentException, ParseException;
 
 
     /**
      * 向上级回复通道信息
-     * @param channel 通道信息
+     *
+     * @param channel        通道信息
      * @param parentPlatform 平台信息
      * @param sn
      * @param fromTag
      * @param size
      * @return
      */
-    void catalogQuery(DeviceChannel channel, ParentPlatform parentPlatform, String sn, String fromTag, int size) throws SipException, InvalidArgumentException, ParseException;
-    void catalogQuery(List<DeviceChannel> channels, ParentPlatform parentPlatform, String sn, String fromTag) throws InvalidArgumentException, ParseException, SipException;
+    void catalogQuery(DeviceChannel channel, ParentPlatform parentPlatform, String sn, String fromTag, int size)
+            throws SipException, InvalidArgumentException, ParseException;
+
+    void catalogQuery(List<DeviceChannel> channels, ParentPlatform parentPlatform, String sn, String fromTag)
+            throws InvalidArgumentException, ParseException, SipException;
 
     /**
      * 向上级回复DeviceInfo查询信息
+     *
      * @param parentPlatform 平台信息
      * @param sn SN
      * @param fromTag FROM头的tag信息
@@ -63,6 +76,7 @@ public interface ISIPCommanderForPlatform {
 
     /**
      * 向上级回复DeviceStatus查询信息
+     *
      * @param parentPlatform 平台信息
      * @param sn
      * @param fromTag
@@ -72,23 +86,27 @@ public interface ISIPCommanderForPlatform {
 
     /**
      * 向上级回复移动位置订阅消息
+     *
      * @param parentPlatform 平台信息
-     * @param gpsMsgInfo GPS信息
-     * @param subscribeInfo 订阅相关的信息
+     * @param gpsMsgInfo     GPS信息
+     * @param subscribeInfo  订阅相关的信息
      * @return
      */
-    void sendNotifyMobilePosition(ParentPlatform parentPlatform, GPSMsgInfo gpsMsgInfo, SubscribeInfo subscribeInfo) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException;
+    void sendNotifyMobilePosition(ParentPlatform parentPlatform, GPSMsgInfo gpsMsgInfo, SubscribeInfo subscribeInfo)
+            throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException;
 
     /**
      * 向上级回复报警消息
+     *
      * @param parentPlatform 平台信息
-     * @param deviceAlarm 报警信息信息
+     * @param deviceAlarm    报警信息信息
      * @return
      */
     void sendAlarmMessage(ParentPlatform parentPlatform, DeviceAlarm deviceAlarm) throws SipException, InvalidArgumentException, ParseException;
 
     /**
      * 回复catalog事件-增加/更新
+     *
      * @param parentPlatform
      * @param deviceChannels
      */
@@ -96,22 +114,28 @@ public interface ISIPCommanderForPlatform {
 
     /**
      * 回复catalog事件-删除
+     *
      * @param parentPlatform
      * @param deviceChannels
      */
-    void sendNotifyForCatalogOther(String type, ParentPlatform parentPlatform, List<DeviceChannel> deviceChannels, SubscribeInfo subscribeInfo, Integer index) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException;
+    void sendNotifyForCatalogOther(String type, ParentPlatform parentPlatform, List<DeviceChannel> deviceChannels,
+                                   SubscribeInfo subscribeInfo, Integer index) throws InvalidArgumentException,
+            ParseException, NoSuchFieldException, SipException, IllegalAccessException;
 
     /**
      * 回复recordInfo
-     * @param deviceChannel 通道信息
+     *
+     * @param deviceChannel  通道信息
      * @param parentPlatform 平台信息
-     * @param fromTag fromTag
-     * @param recordInfo 录像信息
+     * @param fromTag        fromTag
+     * @param recordInfo     录像信息
      */
-    void recordInfo(DeviceChannel deviceChannel, ParentPlatform parentPlatform, String fromTag, RecordInfo recordInfo) throws SipException, InvalidArgumentException, ParseException;
+    void recordInfo(DeviceChannel deviceChannel, ParentPlatform parentPlatform, String fromTag, RecordInfo recordInfo)
+            throws SipException, InvalidArgumentException, ParseException;
 
     /**
      * 录像播放推送完成时发送MediaStatus消息
+     *
      * @param platform
      * @param sendRtpItem
      * @return
@@ -120,9 +144,19 @@ public interface ISIPCommanderForPlatform {
 
     /**
      * 向发起点播的上级回复bye
+     *
      * @param platform 平台信息
-     * @param callId  callId
+     * @param callId   callId
      */
     void streamByeCmd(ParentPlatform platform, String callId) throws SipException, InvalidArgumentException, ParseException;
+
     void streamByeCmd(ParentPlatform platform, SendRtpItem sendRtpItem) throws SipException, InvalidArgumentException, ParseException;
+
+    void streamByeCmd(ParentPlatform platform, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException;
+
+    void broadcastInviteCmd(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem,
+                            SSRCInfo ssrcInfo, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent,
+                            SipSubscribe.Event errorEvent) throws ParseException, SipException, InvalidArgumentException;
+
+    void broadcastResultCmd(ParentPlatform platform, DeviceChannel deviceChannel, String sn, boolean result, SipSubscribe.Event errorEvent,  SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
 }

+ 81 - 4
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java

@@ -4,6 +4,7 @@ import com.genersoft.iot.vmp.conf.SipConfig;
 import com.genersoft.iot.vmp.gb28181.SipLayer;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
+import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
 import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo;
 import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
@@ -55,7 +56,7 @@ public class SIPRequestHeaderPlarformProvider {
 		//via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
 		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(),
-				Integer.parseInt(parentPlatform.getDevicePort()), parentPlatform.getTransport(), SipUtils.getNewViaTag());
+				parentPlatform.getDevicePort(), parentPlatform.getTransport(), SipUtils.getNewViaTag());
 		viaHeader.setRPort();
 		viaHeaders.add(viaHeader);
 		//from
@@ -182,7 +183,7 @@ public class SIPRequestHeaderPlarformProvider {
 		SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), serverAddress);
 		// via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), Integer.parseInt(parentPlatform.getDevicePort()),
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), parentPlatform.getDevicePort(),
 				parentPlatform.getTransport(), viaTag);
 		viaHeader.setRPort();
 		viaHeaders.add(viaHeader);
@@ -219,7 +220,7 @@ public class SIPRequestHeaderPlarformProvider {
 		SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP()+ ":" + parentPlatform.getServerPort());
 		// via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<>();
-		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), Integer.parseInt(parentPlatform.getDevicePort()),
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), parentPlatform.getDevicePort(),
 				parentPlatform.getTransport(), SipUtils.getNewViaTag());
 		viaHeader.setRPort();
 		viaHeaders.add(viaHeader);
@@ -279,7 +280,7 @@ public class SIPRequestHeaderPlarformProvider {
 		SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(platform.getServerGBId(), platform.getServerIP()+ ":" + platform.getServerPort());
 		// via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<>();
-		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(platform.getDeviceIp(), Integer.parseInt(platform.getDevicePort()),
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(platform.getDeviceIp(), platform.getDevicePort(),
 				platform.getTransport(), SipUtils.getNewViaTag());
 		viaHeader.setRPort();
 		viaHeaders.add(viaHeader);
@@ -311,6 +312,82 @@ public class SIPRequestHeaderPlarformProvider {
 
 		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
 
+		return request;
+	}
+
+    public Request createInviteRequest(ParentPlatform platform, String channelId, String content, String viaTag, String fromTag, String ssrc, CallIdHeader callIdHeader) throws PeerUnavailableException, ParseException, InvalidArgumentException {
+		Request request = null;
+		//请求行
+		String platformHostAddress = platform.getServerIP() + ":" + platform.getServerPort();
+		String localHostAddress = sipLayer.getLocalIp(platform.getDeviceIp())+":"+ platform.getDevicePort();
+		SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, platformHostAddress);
+		//via
+		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(platform.getDeviceIp()), platform.getDevicePort(), platform.getTransport(), viaTag);
+		viaHeader.setRPort();
+		viaHeaders.add(viaHeader);
+
+		//from
+		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(platform.getDeviceGBId(), sipConfig.getDomain());
+		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack
+		//to
+		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, platformHostAddress);
+		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,null);
+
+		//Forwards
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
+
+		//ceq
+		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE);
+		request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
+
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
+
+		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(),localHostAddress));
+		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
+		// Subject
+		SubjectHeader subjectHeader = SipFactory.getInstance().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0));
+		request.addHeader(subjectHeader);
+		ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
+		request.setContent(content, contentTypeHeader);
+		return request;
+    }
+
+	public Request createByteRequest(ParentPlatform platform, String channelId, SipTransactionInfo transactionInfo) throws PeerUnavailableException, ParseException, InvalidArgumentException {
+		String deviceHostAddress = platform.getDeviceIp() + ":" + platform.getDevicePort();
+		Request request = null;
+		SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, deviceHostAddress);
+
+		// via
+		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(platform.getDeviceIp()), platform.getDevicePort(), platform.getTransport(), SipUtils.getNewViaTag());
+		viaHeaders.add(viaHeader);
+		//from
+		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain());
+		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.isAsSender()?transactionInfo.getFromTag():transactionInfo.getToTag());
+		//to
+		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, deviceHostAddress);
+		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,transactionInfo.isAsSender()?transactionInfo.getToTag():transactionInfo.getFromTag());
+
+		//Forwards
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
+
+		//ceq
+		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE);
+		CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId());
+		request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.BYE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
+
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
+
+		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(platform.getDeviceIp())+":"+ platform.getDevicePort()));
+		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
+
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
+
 		return request;
 	}
 }

+ 387 - 320
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java

@@ -1,320 +1,387 @@
-package com.genersoft.iot.vmp.gb28181.transmit.cmd;
-
-import com.genersoft.iot.vmp.conf.SipConfig;
-import com.genersoft.iot.vmp.gb28181.SipLayer;
-import com.genersoft.iot.vmp.gb28181.bean.Device;
-import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
-import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
-import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
-import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
-import com.genersoft.iot.vmp.utils.GitUtil;
-import gov.nist.javax.sip.message.SIPRequest;
-import gov.nist.javax.sip.message.SIPResponse;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-import javax.sip.InvalidArgumentException;
-import javax.sip.PeerUnavailableException;
-import javax.sip.SipException;
-import javax.sip.SipFactory;
-import javax.sip.address.Address;
-import javax.sip.address.SipURI;
-import javax.sip.header.*;
-import javax.sip.message.Request;
-import java.text.ParseException;
-import java.util.ArrayList;
-
-/**
- * @description:摄像头命令request创造器 TODO 冗余代码太多待优化
- * @author: swwheihei
- * @date: 2020年5月6日 上午9:29:02
- */
-@Component
-public class SIPRequestHeaderProvider {
-
-	@Autowired
-	private SipConfig sipConfig;
-	
-	@Autowired
-	private SipLayer sipLayer;
-
-	@Autowired
-	private GitUtil gitUtil;
-
-	@Autowired
-	private IRedisCatchStorage redisCatchStorage;
-
-	@Autowired
-	private VideoStreamSessionManager streamSession;
-	
-	public Request createMessageRequest(Device device, String content, String viaTag, String fromTag, String toTag, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
-		Request request = null;
-		// sipuri
-		SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
-		// via
-		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag);
-		viaHeader.setRPort();
-		viaHeaders.add(viaHeader);
-		// from
-		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
-		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, fromTag);
-		// to
-		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
-		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, toTag);
-
-		// Forwards
-		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
-		// ceq
-		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.MESSAGE);
-
-		request = SipFactory.getInstance().createMessageFactory().createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader,
-				toHeader, viaHeaders, maxForwards);
-
-		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
-
-		ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
-		request.setContent(content, contentTypeHeader);
-		return request;
-	}
-	
-	public Request createInviteRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag, String ssrc, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
-		Request request = null;
-		//请求行
-		SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
-		//via
-		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		HeaderFactory headerFactory = SipFactory.getInstance().createHeaderFactory();
-		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag);
-		viaHeader.setRPort();
-		viaHeaders.add(viaHeader);
-
-		//from
-		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
-		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack
-		//to
-		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
-		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,null);
-		
-		//Forwards
-		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
-		
-		//ceq
-		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE);
-		request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
-
-		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
-
-		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
-		// Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), device.getHost().getIp()+":"+device.getHost().getPort()));
-		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
-		// Subject
-		SubjectHeader subjectHeader = SipFactory.getInstance().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0));
-		request.addHeader(subjectHeader);
-		ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
-		request.setContent(content, contentTypeHeader);
-		return request;
-	}
-	
-	public Request createPlaybackInviteRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag, CallIdHeader callIdHeader, String ssrc) throws ParseException, InvalidArgumentException, PeerUnavailableException {
-		Request request = null;
-		//请求行
-		SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
-		// via
-		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag);
-		viaHeader.setRPort();
-		viaHeaders.add(viaHeader);
-		//from
-		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
-		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack
-		//to
-		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
-		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,null);
-		
-		//Forwards
-		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
-		
-		//ceq
-		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE);
-		request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
-		
-		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
-		// Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), device.getHost().getIp()+":"+device.getHost().getPort()));
-		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
-
-		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
-
-		// Subject
-		SubjectHeader subjectHeader = SipFactory.getInstance().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0));
-		request.addHeader(subjectHeader);
-
-		ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
-		request.setContent(content, contentTypeHeader);
-		return request;
-	}
-
-	public Request createByteRequest(Device device, String channelId, SipTransactionInfo transactionInfo) throws ParseException, InvalidArgumentException, PeerUnavailableException {
-		Request request = null;
-		//请求行
-		SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
-//		SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
-		// via
-		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag());
-		viaHeaders.add(viaHeader);
-		//from
-		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain());
-		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.getFromTag());
-		//to
-		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId,device.getHostAddress());
-//		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(),device.getHostAddress());
-		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,	transactionInfo.getToTag());
-
-		//Forwards
-		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
-
-		//ceq
-		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE);
-		CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId());
-		request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.BYE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
-
-		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
-
-		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
-		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
-
-		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
-
-		return request;
-	}
-
-	public Request createSubscribeRequest(Device device, String content, SIPRequest requestOld, Integer expires, String event, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
-		Request request = null;
-		// sipuri
-		SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
-		// via
-		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(),
-				device.getTransport(), SipUtils.getNewViaTag());
-		viaHeader.setRPort();
-		viaHeaders.add(viaHeader);
-		// from
-		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
-		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, requestOld == null ? SipUtils.getNewFromTag() :requestOld.getFromTag());
-		// to
-		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
-		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, requestOld == null ? null :requestOld.getToTag());
-
-		// Forwards
-		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
-
-		// ceq
-		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.SUBSCRIBE);
-
-		request = SipFactory.getInstance().createMessageFactory().createRequest(requestURI, Request.SUBSCRIBE, callIdHeader, cSeqHeader, fromHeader,
-				toHeader, viaHeaders, maxForwards);
-
-
-		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
-		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
-
-		// Expires
-		ExpiresHeader expireHeader = SipFactory.getInstance().createHeaderFactory().createExpiresHeader(expires);
-		request.addHeader(expireHeader);
-
-		// Event
-		EventHeader eventHeader = SipFactory.getInstance().createHeaderFactory().createEventHeader(event);
-
-		int random = (int) Math.floor(Math.random() * 10000);
-		eventHeader.setEventId(random + "");
-		request.addHeader(eventHeader);
-
-		ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
-		request.setContent(content, contentTypeHeader);
-
-		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
-
-		return request;
-	}
-
-	public SIPRequest createInfoRequest(Device device, String channelId, String content, SipTransactionInfo transactionInfo)
-			throws SipException, ParseException, InvalidArgumentException {
-		if (device == null || transactionInfo == null) {
-			return null;
-		}
-		SIPRequest request = null;
-		//请求行
-		SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
-		// via
-		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag());
-		viaHeaders.add(viaHeader);
-		//from
-		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain());
-		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.getFromTag());
-		//to
-		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId,device.getHostAddress());
-		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,	transactionInfo.getToTag());
-
-		//Forwards
-		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
-
-		//ceq
-		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INFO);
-		CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId());
-		request = (SIPRequest)SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.INFO, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
-
-		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
-
-		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
-		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
-
-		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
-
-		if (content != null) {
-			ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application",
-					"MANSRTSP");
-			request.setContent(content, contentTypeHeader);
-		}
-		return request;
-	}
-
-	public Request createAckRequest(String localIp, SipURI sipURI, SIPResponse sipResponse) throws ParseException, InvalidArgumentException, PeerUnavailableException {
-
-
-		// via
-		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(localIp, sipConfig.getPort(), sipResponse.getTopmostViaHeader().getTransport(), SipUtils.getNewViaTag());
-		viaHeaders.add(viaHeader);
-
-		//Forwards
-		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
-
-		//ceq
-		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(sipResponse.getCSeqHeader().getSeqNumber(), Request.ACK);
-
-		Request request = SipFactory.getInstance().createMessageFactory().createRequest(sipURI, Request.ACK, sipResponse.getCallIdHeader(), cSeqHeader, sipResponse.getFromHeader(), sipResponse.getToHeader(), viaHeaders, maxForwards);
-
-		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
-
-		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), localIp + ":"+sipConfig.getPort()));
-		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
-
-		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
-
-		return request;
-	}
-}
+package com.genersoft.iot.vmp.gb28181.transmit.cmd;
+
+import com.genersoft.iot.vmp.conf.SipConfig;
+import com.genersoft.iot.vmp.gb28181.SipLayer;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
+import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
+import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
+import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
+import com.genersoft.iot.vmp.utils.GitUtil;
+import gov.nist.javax.sip.message.SIPRequest;
+import gov.nist.javax.sip.message.SIPResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.sip.InvalidArgumentException;
+import javax.sip.PeerUnavailableException;
+import javax.sip.SipException;
+import javax.sip.SipFactory;
+import javax.sip.address.Address;
+import javax.sip.address.SipURI;
+import javax.sip.header.*;
+import javax.sip.message.Request;
+import java.text.ParseException;
+import java.util.ArrayList;
+
+/**
+ * @description:摄像头命令request创造器 TODO 冗余代码太多待优化
+ * @author: swwheihei
+ * @date: 2020年5月6日 上午9:29:02
+ */
+@Component
+public class SIPRequestHeaderProvider {
+
+	@Autowired
+	private SipConfig sipConfig;
+	
+	@Autowired
+	private SipLayer sipLayer;
+
+	@Autowired
+	private GitUtil gitUtil;
+
+	@Autowired
+	private IRedisCatchStorage redisCatchStorage;
+
+	@Autowired
+	private VideoStreamSessionManager streamSession;
+	
+	public Request createMessageRequest(Device device, String content, String viaTag, String fromTag, String toTag, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
+		Request request = null;
+		// sipuri
+		SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
+		// via
+		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag);
+		viaHeader.setRPort();
+		viaHeaders.add(viaHeader);
+		// from
+		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
+		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, fromTag);
+		// to
+		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
+		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, toTag);
+
+		// Forwards
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
+		// ceq
+		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.MESSAGE);
+
+		request = SipFactory.getInstance().createMessageFactory().createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader,
+				toHeader, viaHeaders, maxForwards);
+
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
+
+		ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
+		request.setContent(content, contentTypeHeader);
+		return request;
+	}
+	
+	public Request createInviteRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag, String ssrc, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
+		Request request = null;
+		//请求行
+		SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
+		//via
+		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+		HeaderFactory headerFactory = SipFactory.getInstance().createHeaderFactory();
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag);
+		viaHeader.setRPort();
+		viaHeaders.add(viaHeader);
+
+		//from
+		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
+		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack
+		//to
+		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
+		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,null);
+		
+		//Forwards
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
+		
+		//ceq
+		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE);
+		request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
+
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
+
+		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
+		// Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), device.getHost().getIp()+":"+device.getHost().getPort()));
+		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
+		// Subject
+		SubjectHeader subjectHeader = SipFactory.getInstance().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0));
+		request.addHeader(subjectHeader);
+		ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
+		request.setContent(content, contentTypeHeader);
+		return request;
+	}
+	
+	public Request createPlaybackInviteRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag, CallIdHeader callIdHeader, String ssrc) throws ParseException, InvalidArgumentException, PeerUnavailableException {
+		Request request = null;
+		//请求行
+		SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
+		// via
+		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag);
+		viaHeader.setRPort();
+		viaHeaders.add(viaHeader);
+		//from
+		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
+		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack
+		//to
+		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
+		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,null);
+		
+		//Forwards
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
+		
+		//ceq
+		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE);
+		request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
+		
+		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
+		// Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), device.getHost().getIp()+":"+device.getHost().getPort()));
+		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
+
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
+
+		// Subject
+		SubjectHeader subjectHeader = SipFactory.getInstance().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0));
+		request.addHeader(subjectHeader);
+
+		ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
+		request.setContent(content, contentTypeHeader);
+		return request;
+	}
+
+	public Request createByteRequest(Device device, String channelId, SipTransactionInfo transactionInfo) throws ParseException, InvalidArgumentException, PeerUnavailableException {
+		Request request = null;
+		//请求行
+		SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
+//		SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
+		// via
+		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag());
+		viaHeaders.add(viaHeader);
+		//from
+		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain());
+		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.getFromTag());
+		//to
+		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId,device.getHostAddress());
+//		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(),device.getHostAddress());
+		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,	transactionInfo.getToTag());
+
+		//Forwards
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
+
+		//ceq
+		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE);
+		CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId());
+		request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.BYE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
+
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
+
+		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
+		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
+
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
+
+		return request;
+	}
+
+	public Request createByteRequestForDeviceInvite(Device device, String channelId, SipTransactionInfo transactionInfo) throws ParseException, InvalidArgumentException, PeerUnavailableException {
+		Request request = null;
+		//请求行
+		SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
+		// via
+		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag());
+		viaHeaders.add(viaHeader);
+		//from
+		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain());
+		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.getToTag());
+		//to
+		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId,device.getHostAddress());
+		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, transactionInfo.getFromTag());
+
+		//Forwards
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
+
+		//ceq
+		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE);
+		CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId());
+		request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.BYE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
+
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
+
+		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
+		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
+
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
+
+		return request;
+	}
+
+	public Request createSubscribeRequest(Device device, String content, SIPRequest requestOld, Integer expires, String event, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
+		Request request = null;
+		// sipuri
+		SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
+		// via
+		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(),
+				device.getTransport(), SipUtils.getNewViaTag());
+		viaHeader.setRPort();
+		viaHeaders.add(viaHeader);
+		// from
+		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
+		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, requestOld == null ? SipUtils.getNewFromTag() :requestOld.getFromTag());
+		// to
+		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
+		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, requestOld == null ? null :requestOld.getToTag());
+
+		// Forwards
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
+
+		// ceq
+		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.SUBSCRIBE);
+
+		request = SipFactory.getInstance().createMessageFactory().createRequest(requestURI, Request.SUBSCRIBE, callIdHeader, cSeqHeader, fromHeader,
+				toHeader, viaHeaders, maxForwards);
+
+
+		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
+		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
+
+		// Expires
+		ExpiresHeader expireHeader = SipFactory.getInstance().createHeaderFactory().createExpiresHeader(expires);
+		request.addHeader(expireHeader);
+
+		// Event
+		EventHeader eventHeader = SipFactory.getInstance().createHeaderFactory().createEventHeader(event);
+
+		int random = (int) Math.floor(Math.random() * 10000);
+		eventHeader.setEventId(random + "");
+		request.addHeader(eventHeader);
+
+		ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
+		request.setContent(content, contentTypeHeader);
+
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
+
+		return request;
+	}
+
+	public SIPRequest createInfoRequest(Device device, String channelId, String content, SipTransactionInfo transactionInfo)
+			throws SipException, ParseException, InvalidArgumentException {
+		if (device == null || transactionInfo == null) {
+			return null;
+		}
+		SIPRequest request = null;
+		//请求行
+		SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
+		// via
+		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag());
+		viaHeaders.add(viaHeader);
+		//from
+		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain());
+		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.getFromTag());
+		//to
+		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId,device.getHostAddress());
+		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,	transactionInfo.getToTag());
+
+		//Forwards
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
+
+		//ceq
+		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INFO);
+		CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId());
+		request = (SIPRequest)SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.INFO, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
+
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
+
+		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
+		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
+
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
+
+		if (content != null) {
+			ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application",
+					"MANSRTSP");
+			request.setContent(content, contentTypeHeader);
+		}
+		return request;
+	}
+
+	public Request createAckRequest(String localIp, SipURI sipURI, SIPResponse sipResponse) throws ParseException, InvalidArgumentException, PeerUnavailableException {
+
+
+		// via
+		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(localIp, sipConfig.getPort(), sipResponse.getTopmostViaHeader().getTransport(), SipUtils.getNewViaTag());
+		viaHeaders.add(viaHeader);
+
+		//Forwards
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
+
+		//ceq
+		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(sipResponse.getCSeqHeader().getSeqNumber(), Request.ACK);
+
+		Request request = SipFactory.getInstance().createMessageFactory().createRequest(sipURI, Request.ACK, sipResponse.getCallIdHeader(), cSeqHeader, sipResponse.getFromHeader(), sipResponse.getToHeader(), viaHeaders, maxForwards);
+
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
+
+		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), localIp + ":"+sipConfig.getPort()));
+		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
+
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
+
+		return request;
+	}
+	public Request createBroadcastMessageRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
+		Request request = null;
+		// sipuri
+		SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
+		// via
+		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipConfig.getIp(), sipConfig.getPort(), device.getTransport(), viaTag);
+		viaHeader.setRPort();
+		viaHeaders.add(viaHeader);
+		// from
+		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
+		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, fromTag);
+		// to
+		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
+		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, toTag);
+
+		// Forwards
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
+		// ceq
+		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.MESSAGE);
+
+		ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
+
+		request = SipFactory.getInstance().createMessageFactory().createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader,
+				toHeader, viaHeaders, maxForwards, contentTypeHeader, content);
+
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
+
+		return request;
+	}
+}

ファイルの差分が大きいため隠しています
+ 1473 - 1424
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java


+ 131 - 6
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java

@@ -1,24 +1,34 @@
 package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
 
 import com.alibaba.fastjson2.JSON;
+import com.genersoft.iot.vmp.common.InviteSessionType;
 import com.genersoft.iot.vmp.conf.DynamicTask;
+import com.genersoft.iot.vmp.conf.UserSetting;
+import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
 import com.genersoft.iot.vmp.gb28181.SipLayer;
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
+import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
 import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderPlarformProvider;
 import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
+import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
+import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
+import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
 import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.hook.HookParam;
 import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
+import com.genersoft.iot.vmp.service.bean.SSRCInfo;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo;
 import com.genersoft.iot.vmp.utils.DateUtil;
 import com.genersoft.iot.vmp.utils.GitUtil;
 import gov.nist.javax.sip.message.MessageFactoryImpl;
 import gov.nist.javax.sip.message.SIPRequest;
+import gov.nist.javax.sip.message.SIPResponse;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -28,6 +38,7 @@ import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
 
 import javax.sip.InvalidArgumentException;
+import javax.sip.ResponseEvent;
 import javax.sip.SipException;
 import javax.sip.SipFactory;
 import javax.sip.header.CallIdHeader;
@@ -64,6 +75,16 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
     @Autowired
     private SIPSender sipSender;
 
+    @Autowired
+    private ZlmHttpHookSubscribe subscribe;
+
+    @Autowired
+    private UserSetting userSetting;
+
+
+    @Autowired
+    private VideoStreamSessionManager streamSession;
+
     @Autowired
     private DynamicTask dynamicTask;
 
@@ -462,6 +483,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         sipSender.transmitRequest(parentPlatform.getDeviceIp(), request);
     }
 
+
     /**
      * 向上级回复DeviceStatus查询信息
      * @param parentPlatform 平台信息
@@ -811,26 +833,129 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
     }
 
     @Override
-    public void streamByeCmd(ParentPlatform parentPlatform, SendRtpItem sendRtpItem) throws SipException, InvalidArgumentException, ParseException {
+    public synchronized void streamByeCmd(ParentPlatform platform, SendRtpItem sendRtpItem) throws SipException, InvalidArgumentException, ParseException {
         if (sendRtpItem == null ) {
             logger.info("[向上级发送BYE], sendRtpItem 为NULL");
             return;
         }
-        if (parentPlatform == null) {
+        if (platform == null) {
             logger.info("[向上级发送BYE], platform 为NULL");
             return;
         }
-        logger.info("[向上级发送BYE], {}/{}", parentPlatform.getServerGBId(), sendRtpItem.getChannelId());
+        logger.info("[向上级发送BYE], {}/{}", platform.getServerGBId(), sendRtpItem.getChannelId());
         String mediaServerId = sendRtpItem.getMediaServerId();
         MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
         if (mediaServerItem != null) {
             mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc());
-            zlmServerFactory.closeRtpServer(mediaServerItem, sendRtpItem.getStreamId());
+            zlmServerFactory.closeRtpServer(mediaServerItem, sendRtpItem.getStream());
         }
-        SIPRequest byeRequest = headerProviderPlatformProvider.createByeRequest(parentPlatform, sendRtpItem);
+        SIPRequest byeRequest = headerProviderPlatformProvider.createByeRequest(platform, sendRtpItem);
         if (byeRequest == null) {
             logger.warn("[向上级发送bye]:无法创建 byeRequest");
         }
-        sipSender.transmitRequest(parentPlatform.getDeviceIp(),byeRequest);
+        sipSender.transmitRequest(platform.getDeviceIp(),byeRequest);
+    }
+
+    @Override
+    public void streamByeCmd(ParentPlatform platform, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException {
+        SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(platform.getServerGBId(), channelId, callId, stream);
+        if (ssrcTransaction == null) {
+            throw new SsrcTransactionNotFoundException(platform.getServerGBId(), channelId, callId, stream);
+        }
+
+        mediaServerService.releaseSsrc(ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc());
+        mediaServerService.closeRTPServer(ssrcTransaction.getMediaServerId(), ssrcTransaction.getStream());
+        streamSession.remove(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
+
+        Request byteRequest = headerProviderPlatformProvider.createByteRequest(platform, channelId, ssrcTransaction.getSipTransactionInfo());
+        sipSender.transmitRequest(sipLayer.getLocalIp(platform.getDeviceIp()), byteRequest, null, okEvent);
+    }
+
+    @Override
+    public void broadcastResultCmd(ParentPlatform platform, DeviceChannel deviceChannel, String sn, boolean result, SipSubscribe.Event errorEvent,  SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
+        if (platform == null || deviceChannel == null) {
+            return;
+        }
+        String characterSet = platform.getCharacterSet();
+        StringBuffer mediaStatusXml = new StringBuffer(200);
+        mediaStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
+        mediaStatusXml.append("<Response>\r\n");
+        mediaStatusXml.append("<CmdType>Broadcast</CmdType>\r\n");
+        mediaStatusXml.append("<SN>" + sn + "</SN>\r\n");
+        mediaStatusXml.append("<DeviceID>" + deviceChannel.getChannelId() + "</DeviceID>\r\n");
+        mediaStatusXml.append("<Result>" + (result?"OK":"ERROR") + "</Result>\r\n");
+        mediaStatusXml.append("</Response>\r\n");
+
+        CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(platform.getDeviceIp(), platform.getTransport());
+
+        SIPRequest messageRequest = (SIPRequest)headerProviderPlatformProvider.createMessageRequest(platform, mediaStatusXml.toString(),
+               SipUtils.getNewFromTag(), SipUtils.getNewViaTag(), callIdHeader);
+
+        sipSender.transmitRequest(platform.getDeviceIp(),messageRequest, errorEvent, okEvent);
+    }
+
+    @Override
+    public void broadcastInviteCmd(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem,
+                                   SSRCInfo ssrcInfo, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent,
+                                   SipSubscribe.Event errorEvent) throws ParseException, SipException, InvalidArgumentException {
+        String stream = ssrcInfo.getStream();
+
+        if (platform == null) {
+            return;
+        }
+
+        logger.info("{} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
+        HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId());
+        subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, HookParam hookParam) -> {
+            if (event != null) {
+                event.response(mediaServerItemInUse, hookParam);
+                subscribe.removeSubscribe(hookSubscribe);
+            }
+        });
+        String sdpIp = mediaServerItem.getSdpIp();
+
+        StringBuffer content = new StringBuffer(200);
+        content.append("v=0\r\n");
+        content.append("o=" + channelId + " 0 0 IN IP4 " + sdpIp + "\r\n");
+        content.append("s=Play\r\n");
+        content.append("c=IN IP4 " + sdpIp + "\r\n");
+        content.append("t=0 0\r\n");
+
+        if ("TCP-PASSIVE".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) {
+            content.append("m=audio " + ssrcInfo.getPort() + " TCP/RTP/AVP 8 96\r\n");
+        } else if ("TCP-ACTIVE".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) {
+            content.append("m=audio " + ssrcInfo.getPort() + " TCP/RTP/AVP 8 96\r\n");
+        } else if ("UDP".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) {
+            content.append("m=audio " + ssrcInfo.getPort() + " RTP/AVP 8 96\r\n");
+        }
+
+        content.append("a=recvonly\r\n");
+        content.append("a=rtpmap:8 PCMA/8000\r\n");
+        content.append("a=rtpmap:96 PS/90000\r\n");
+        if ("TCP-PASSIVE".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) {
+            content.append("a=setup:passive\r\n");
+            content.append("a=connection:new\r\n");
+        }else if ("TCP-ACTIVE".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) {
+            content.append("a=setup:active\r\n");
+            content.append("a=connection:new\r\n");
+        }
+
+        content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc
+        CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(platform.getDeviceIp()), platform.getTransport());
+
+        Request request = headerProviderPlatformProvider.createInviteRequest(platform, channelId,
+                content.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(),  ssrcInfo.getSsrc(),
+                callIdHeader);
+        sipSender.transmitRequest(sipLayer.getLocalIp(platform.getDeviceIp()), request, (e -> {
+            streamSession.remove(platform.getServerGBId(), channelId, ssrcInfo.getStream());
+            mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
+            subscribe.removeSubscribe(hookSubscribe);
+            errorEvent.response(e);
+        }), e -> {
+            ResponseEvent responseEvent = (ResponseEvent) e.event;
+            SIPResponse response = (SIPResponse) responseEvent.getResponse();
+            streamSession.put(platform.getServerGBId(), channelId, callIdHeader.getCallId(),  stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response, InviteSessionType.BROADCAST);
+            okEvent.response(e);
+        });
     }
 }

+ 10 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorParent.java

@@ -82,6 +82,7 @@ public abstract class SIPRequestProcessorParent {
 		return responseAck(sipRequest, statusCode, msg, null);
 	}
 
+
 	public SIPResponse responseAck(SIPRequest sipRequest, int statusCode, String msg, ResponseAckExtraParam responseAckExtraParam) throws SipException, InvalidArgumentException, ParseException {
 		if (sipRequest.getToHeader().getTag() == null) {
 			sipRequest.getToHeader().setTag(SipUtils.getNewTag());
@@ -124,6 +125,8 @@ public abstract class SIPRequestProcessorParent {
 		return response;
 	}
 
+
+
 	/**
 	 * 回复带sdp的200
 	 */
@@ -141,7 +144,10 @@ public abstract class SIPRequestProcessorParent {
 		responseAckExtraParam.content = sdp;
 		responseAckExtraParam.sipURI = sipURI;
 
-		return responseAck(request, Response.OK, null, responseAckExtraParam);
+		SIPResponse sipResponse = responseAck(request, Response.OK, null, responseAckExtraParam);
+
+
+		return sipResponse;
 	}
 
 	/**
@@ -174,7 +180,8 @@ public abstract class SIPRequestProcessorParent {
 		reader.setEncoding(charset);
 		// 对海康出现的未转义字符做处理。
 		String[] destStrArray = new String[]{"&lt;","&gt;","&amp;","&apos;","&quot;"};
-		char despChar = '&'; // 或许可扩展兼容其他字符
+		// 或许可扩展兼容其他字符
+		char despChar = '&';
 		byte destBye = (byte) despChar;
 		List<Byte> result = new ArrayList<>();
 		byte[] rawContent = request.getRawContent();
@@ -220,4 +227,5 @@ public abstract class SIPRequestProcessorParent {
 		return xml.getRootElement();
 	}
 
+
 }

+ 82 - 70
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java

@@ -1,22 +1,20 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
 
-import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.conf.DynamicTask;
 import com.genersoft.iot.vmp.conf.UserSetting;
-import com.genersoft.iot.vmp.gb28181.bean.InviteStreamType;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
 import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
-import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
-import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
 import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
 import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import com.genersoft.iot.vmp.service.IDeviceService;
 import com.genersoft.iot.vmp.service.IMediaServerService;
-import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
+import com.genersoft.iot.vmp.service.IPlayService;
 import com.genersoft.iot.vmp.service.bean.RequestPushStreamMsg;
 import com.genersoft.iot.vmp.service.redisMsg.RedisGbPlayMsgListener;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
@@ -27,25 +25,23 @@ import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
-import javax.sip.InvalidArgumentException;
 import javax.sip.RequestEvent;
-import javax.sip.SipException;
 import javax.sip.address.SipURI;
 import javax.sip.header.CallIdHeader;
 import javax.sip.header.FromHeader;
 import javax.sip.header.HeaderAddress;
 import javax.sip.header.ToHeader;
-import java.text.ParseException;
 import java.util.HashMap;
 import java.util.Map;
 
 /**
  * SIP命令类型: ACK请求
+ * @author lin
  */
 @Component
 public class AckRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor {
 
-	private Logger logger = LoggerFactory.getLogger(AckRequestProcessor.class);
+	private final Logger logger = LoggerFactory.getLogger(AckRequestProcessor.class);
 	private final String method = "ACK";
 
 	@Autowired
@@ -66,6 +62,9 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
 	@Autowired
 	private IVideoManagerStorage storager;
 
+	@Autowired
+	private IDeviceService deviceService;
+
 	@Autowired
 	private ZLMServerFactory zlmServerFactory;
 
@@ -75,113 +74,126 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
 	@Autowired
 	private IMediaServerService mediaServerService;
 
-	@Autowired
-	private ZlmHttpHookSubscribe subscribe;
-
 	@Autowired
 	private DynamicTask dynamicTask;
 
 	@Autowired
-	private ISIPCommander cmder;
-
-	@Autowired
-	private ISIPCommanderForPlatform commanderForPlatform;
+	private RedisGbPlayMsgListener redisGbPlayMsgListener;
 
 	@Autowired
-	private RedisGbPlayMsgListener redisGbPlayMsgListener;
+	private IPlayService playService;
 
 
 	/**   
 	 * 处理  ACK请求
-	 * 
-	 * @param evt
 	 */
 	@Override
 	public void process(RequestEvent evt) {
 		CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME);
-
-		String platformGbId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(FromHeader.NAME)).getAddress().getURI()).getUser();
-		logger.info("[收到ACK]: platformGbId->{}", platformGbId);
-		ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(platformGbId);
-		// 取消设置的超时任务
 		dynamicTask.stop(callIdHeader.getCallId());
-		String channelId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser();
-		SendRtpItem sendRtpItem =  redisCatchStorage.querySendRTPServer(platformGbId, channelId, null, callIdHeader.getCallId());
+		String fromUserId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(FromHeader.NAME)).getAddress().getURI()).getUser();
+		String toUserId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser();
+		logger.info("[收到ACK]: 来自->{}", fromUserId);
+		SendRtpItem sendRtpItem =  redisCatchStorage.querySendRTPServer(null, null, null, callIdHeader.getCallId());
 		if (sendRtpItem == null) {
-			logger.warn("[收到ACK]:未找到通道({})的推流信息", channelId);
+			logger.warn("[收到ACK]:未找到来自{},目标为({})的推流信息",fromUserId, toUserId);
 			return;
 		}
 		// tcp主动时,此时是级联下级平台,在回复200ok时,本地已经请求zlm开启监听,跳过下面步骤
 		if (sendRtpItem.isTcpActive()) {
-			logger.info("收到ACK,rtp/{} TCP主动方式后续处理", sendRtpItem.getStreamId());
+			logger.info("收到ACK,rtp/{} TCP主动方式后续处理", sendRtpItem.getStream());
 			return;
 		}
-		String is_Udp = sendRtpItem.isTcp() ? "0" : "1";
 		MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
 		logger.info("收到ACK,rtp/{}开始向上级推流, 目标={}:{},SSRC={}, 协议:{}",
-				sendRtpItem.getStreamId(),
+				sendRtpItem.getStream(),
 				sendRtpItem.getIp(),
 				sendRtpItem.getPort(),
 				sendRtpItem.getSsrc(),
 				sendRtpItem.isTcp()?(sendRtpItem.isTcpActive()?"TCP主动":"TCP被动"):"UDP"
 		);
+		ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(fromUserId);
+
+		if (parentPlatform != null) {
+			Map<String, Object> param = getSendRtpParam(sendRtpItem);
+			if (mediaInfo == null) {
+				RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance(
+						sendRtpItem.getMediaServerId(), sendRtpItem.getApp(), sendRtpItem.getStream(),
+						sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isTcp(),
+						sendRtpItem.getLocalPort(), sendRtpItem.getPt(), sendRtpItem.isUsePs(), sendRtpItem.isOnlyAudio());
+				redisGbPlayMsgListener.sendMsgForStartSendRtpStream(sendRtpItem.getServerId(), requestPushStreamMsg, json -> {
+					playService.startSendRtpStreamHand(sendRtpItem, parentPlatform, json, param, callIdHeader);
+				});
+			} else {
+				JSONObject startSendRtpStreamResult = sendRtp(sendRtpItem, mediaInfo, param);
+				if (startSendRtpStreamResult != null) {
+					playService.startSendRtpStreamHand(sendRtpItem, parentPlatform, startSendRtpStreamResult, param, callIdHeader);
+				}
+			}
+		}else {
+			Device device = deviceService.getDevice(fromUserId);
+			if (device == null) {
+				logger.warn("[收到ACK]:来自{},目标为({})的推流信息为找到流体服务[{}]信息",fromUserId, toUserId, sendRtpItem.getMediaServerId());
+				return;
+			}
+			// 设置为收到ACK后发送语音的设备已经在发送200OK开始发流了
+			if (!device.isBroadcastPushAfterAck()) {
+				return;
+			}
+			if (mediaInfo == null) {
+				logger.warn("[收到ACK]:来自{},目标为({})的推流信息为找到流体服务[{}]信息",fromUserId, toUserId, sendRtpItem.getMediaServerId());
+				return;
+			}
+			Map<String, Object> param = getSendRtpParam(sendRtpItem);
+			JSONObject startSendRtpStreamResult = sendRtp(sendRtpItem, mediaInfo, param);
+			if (startSendRtpStreamResult != null) {
+				playService.startSendRtpStreamHand(sendRtpItem, device, startSendRtpStreamResult, param, callIdHeader);
+			}
+		}
+	}
+
+	private Map<String, Object> getSendRtpParam(SendRtpItem sendRtpItem) {
+		String isUdp = sendRtpItem.isTcp() ? "0" : "1";
 		Map<String, Object> param = new HashMap<>(12);
 		param.put("vhost","__defaultVhost__");
 		param.put("app",sendRtpItem.getApp());
-		param.put("stream",sendRtpItem.getStreamId());
+		param.put("stream",sendRtpItem.getStream());
 		param.put("ssrc", sendRtpItem.getSsrc());
 		param.put("dst_url",sendRtpItem.getIp());
 		param.put("dst_port", sendRtpItem.getPort());
-		param.put("is_udp", is_Udp);
 		param.put("src_port", sendRtpItem.getLocalPort());
 		param.put("pt", sendRtpItem.getPt());
 		param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0");
 		param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0");
+		param.put("is_udp", isUdp);
 		if (!sendRtpItem.isTcp()) {
-			// 开启rtcp保活
+			// udp模式下开启rtcp保活
 			param.put("udp_rtcp_timeout", sendRtpItem.isRtcp()? "1":"0");
 		}
-		if (mediaInfo == null) {
-			RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance(
-					sendRtpItem.getMediaServerId(), sendRtpItem.getApp(), sendRtpItem.getStreamId(),
-					sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isTcp(),
-					sendRtpItem.getLocalPort(), sendRtpItem.getPt(), sendRtpItem.isUsePs(), sendRtpItem.isOnlyAudio());
-			redisGbPlayMsgListener.sendMsgForStartSendRtpStream(sendRtpItem.getServerId(), requestPushStreamMsg, jsonObject->{
-				startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, jsonObject, param, callIdHeader);
-			});
-		}else {
-			JSONObject startSendRtpStreamResult = zlmServerFactory.startSendRtpStream(mediaInfo, param);
-			if (startSendRtpStreamResult != null) {
-				startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, startSendRtpStreamResult, param, callIdHeader);
-			}
-		}
+		return param;
 	}
-	private void startSendRtpStreamHand(RequestEvent evt, SendRtpItem sendRtpItem, ParentPlatform parentPlatform,
-										JSONObject jsonObject, Map<String, Object> param, CallIdHeader callIdHeader) {
-		if (jsonObject == null) {
-			logger.error("RTP推流失败: 请检查ZLM服务");
-		} else if (jsonObject.getInteger("code") == 0) {
-			logger.info("调用ZLM推流接口, 结果: {}",  jsonObject);
-			logger.info("RTP推流成功[ {}/{} ],{}->{}:{}, " ,param.get("app"), param.get("stream"), jsonObject.getString("local_port"), param.get("dst_url"), param.get("dst_port"));
-			if (sendRtpItem.getPlayType() == InviteStreamType.PUSH) {
-				MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0, sendRtpItem.getApp(), sendRtpItem.getStreamId(),
-						sendRtpItem.getChannelId(), parentPlatform.getServerGBId(), parentPlatform.getName(), userSetting.getServerId(),
-						sendRtpItem.getMediaServerId());
-				messageForPushChannel.setPlatFormIndex(parentPlatform.getId());
-				redisCatchStorage.sendPlatformStartPlayMsg(messageForPushChannel);
+
+	private JSONObject sendRtp(SendRtpItem sendRtpItem, MediaServerItem mediaInfo, Map<String, Object> param){
+		JSONObject startSendRtpStreamResult = null;
+		if (sendRtpItem.getLocalPort() != 0) {
+			if (sendRtpItem.isTcpActive()) {
+				startSendRtpStreamResult = zlmServerFactory.startSendRtpPassive(mediaInfo, param);
+			}else {
+				param.put("dst_url", sendRtpItem.getIp());
+				param.put("dst_port", sendRtpItem.getPort());
+				startSendRtpStreamResult = zlmServerFactory.startSendRtpStream(mediaInfo, param);
 			}
-		} else {
-			logger.error("RTP推流失败: {}, 参数:{}",jsonObject.getString("msg"), JSON.toJSONString(param));
-			if (sendRtpItem.isOnlyAudio()) {
-				// TODO 可能是语音对讲
+		}else {
+			if (sendRtpItem.isTcpActive()) {
+				startSendRtpStreamResult = zlmServerFactory.startSendRtpPassive(mediaInfo, param);
 			}else {
-				// 向上级平台
-				try {
-					commanderForPlatform.streamByeCmd(parentPlatform, callIdHeader.getCallId());
-				} catch (SipException | InvalidArgumentException | ParseException e) {
-					logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
-				}
+				param.put("dst_url", sendRtpItem.getIp());
+				param.put("dst_port", sendRtpItem.getPort());
+				startSendRtpStreamResult = zlmServerFactory.startSendRtpStream(mediaInfo, param);
 			}
 		}
+		return startSendRtpStreamResult;
+
 	}
+
 }

+ 55 - 6
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java

@@ -5,10 +5,12 @@ import com.genersoft.iot.vmp.common.InviteSessionType;
 import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
 import com.genersoft.iot.vmp.gb28181.bean.*;
+import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
 import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
 import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
+import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
 import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
@@ -45,6 +47,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
 	@Autowired
 	private ISIPCommander cmder;
 
+	@Autowired
+	private ISIPCommanderForPlatform commanderForPlatform;
+
 	@Autowired
 	private IRedisCatchStorage redisCatchStorage;
 
@@ -57,6 +62,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
 	@Autowired
 	private IDeviceService deviceService;
 
+	@Autowired
+	private AudioBroadcastManager audioBroadcastManager;
+
 	@Autowired
 	private IDeviceChannelService channelService;
 
@@ -78,6 +86,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
 	@Autowired
 	private VideoStreamSessionManager streamSession;
 
+	@Autowired
+	private IPlayService playService;
+
 	@Autowired
 	private UserSetting userSetting;
 
@@ -100,18 +111,19 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
 			logger.error("[回复BYE信息失败],{}", e.getMessage());
 		}
 		CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME);
-
 		SendRtpItem sendRtpItem =  redisCatchStorage.querySendRTPServer(null, null, null, callIdHeader.getCallId());
 
+		// 收流端发送的停止
 		if (sendRtpItem != null){
-			logger.info("[收到bye] 来自平台{}, 停止通道:{}", sendRtpItem.getPlatformId(), sendRtpItem.getChannelId());
-			String streamId = sendRtpItem.getStreamId();
+			logger.info("[收到bye] 来自{},停止通道:{}, 类型: {}", sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(), sendRtpItem.getPlayType());
+
+			String streamId = sendRtpItem.getStream();
 			Map<String, Object> param = new HashMap<>();
 			param.put("vhost","__defaultVhost__");
 			param.put("app",sendRtpItem.getApp());
 			param.put("stream",streamId);
 			param.put("ssrc",sendRtpItem.getSsrc());
-			logger.info("[收到bye] 停止向上级推流:{}", streamId);
+			logger.info("[收到bye] 停止推流:{}", streamId);
 			MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
 			redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(),
 					callIdHeader.getCallId(), null);
@@ -123,7 +135,7 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
 				ParentPlatform platform = platformService.queryPlatformByServerGBId(sendRtpItem.getPlatformId());
 				if (platform != null) {
 					MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
-							sendRtpItem.getApp(), sendRtpItem.getStreamId(), sendRtpItem.getChannelId(),
+							sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(),
 							sendRtpItem.getPlatformId(), platform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId());
 					messageForPushChannel.setPlatFormIndex(platform.getId());
 					redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel);
@@ -132,6 +144,13 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
 				}
 			}
 
+			AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
+			if (audioBroadcastCatch != null && audioBroadcastCatch.getSipTransactionInfo().getCallId().equals(callIdHeader.getCallId())) {
+				// 来自上级平台的停止对讲
+				logger.info("[停止对讲] 来自上级,平台:{}, 通道:{}", sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
+				audioBroadcastManager.del(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
+			}
+
 			int totalReaderCount = zlmServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId);
 			if (totalReaderCount <= 0) {
 				logger.info("[收到bye] {} 无其它观看者,通知设备停止推流", streamId);
@@ -149,7 +168,7 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
 					}
 				}
 			}
-		}else {
+		}
 
 			// 可能是设备发送的停止
 			SsrcTransaction ssrcTransaction = streamSession.getSsrcTransactionByCallId(callIdHeader.getCallId());
@@ -158,6 +177,23 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
 			}
 			logger.info("[收到bye] 来自设备:{}, 通道已停止推流: {}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
 
+		ParentPlatform platform = platformService.queryPlatformByServerGBId(ssrcTransaction.getDeviceId());
+		if (platform != null ) {
+			if (ssrcTransaction.getType().equals(InviteSessionType.BROADCAST)) {
+				logger.info("[收到bye] 上级停止语音对讲,来自:{}, 通道已停止推流: {}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
+				DeviceChannel channel = storager.queryChannelInParentPlatform(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
+				if (channel == null) {
+					logger.info("[收到bye] 未找到通道,设备:{}, 通道:{}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
+					return;
+				}
+				String mediaServerId = ssrcTransaction.getMediaServerId();
+				platformService.stopBroadcast(platform, channel, ssrcTransaction.getStream(), false,
+						mediaServerService.getOne(mediaServerId));
+
+				playService.stopAudioBroadcast(channel.getDeviceId(), channel.getChannelId());
+			}
+
+		}else {
 			Device device = deviceService.getDevice(ssrcTransaction.getDeviceId());
 			if (device == null) {
 				logger.info("[收到bye] 未找到设备:{} ", ssrcTransaction.getDeviceId());
@@ -182,6 +218,19 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
 				mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransaction.getSsrc());
 			}
 			streamSession.removeByCallId(device.getDeviceId(), channel.getChannelId(), ssrcTransaction.getCallId());
+			if (ssrcTransaction.getType() == InviteSessionType.BROADCAST) {
+				// 查找来源的对讲设备,发送停止
+				Device sourceDevice = storager.queryVideoDeviceByPlatformIdAndChannelId(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
+				if (sourceDevice != null) {
+					playService.stopAudioBroadcast(sourceDevice.getDeviceId(), channel.getChannelId());
+				}
+			}
+			AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(ssrcTransaction.getDeviceId(), channel.getChannelId());
+			if (audioBroadcastCatch != null) {
+				// 来自上级平台的停止对讲
+				logger.info("[停止对讲] 来自上级,平台:{}, 通道:{}", ssrcTransaction.getDeviceId(), channel.getChannelId());
+				audioBroadcastManager.del(ssrcTransaction.getDeviceId(), channel.getChannelId());
+			}
 		}
 	}
 }

+ 241 - 70
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java

@@ -2,14 +2,19 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
 
 import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONObject;
+import com.genersoft.iot.vmp.common.InviteSessionType;
 import com.genersoft.iot.vmp.common.StreamInfo;
+import com.genersoft.iot.vmp.common.VideoManagerConstants;
 import com.genersoft.iot.vmp.conf.DynamicTask;
+import com.genersoft.iot.vmp.conf.SipConfig;
 import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.*;
+import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
 import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
+import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
 import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
 import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
-import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
+import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
 import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
@@ -65,13 +70,14 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
     private final String method = "INVITE";
 
     @Autowired
-    private SIPCommanderFroPlatform cmderFroPlatform;
+    private ISIPCommanderForPlatform cmderFroPlatform;
 
     @Autowired
     private IVideoManagerStorage storager;
 
     @Autowired
     private IStreamPushService streamPushService;
+
     @Autowired
     private IStreamProxyService streamProxyService;
 
@@ -96,6 +102,9 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
     @Autowired
     private SIPSender sipSender;
 
+    @Autowired
+    private AudioBroadcastManager audioBroadcastManager;
+
     @Autowired
     private ZLMServerFactory zlmServerFactory;
 
@@ -114,10 +123,16 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
     @Autowired
     private ZLMMediaListManager mediaListManager;
 
+    @Autowired
+    private SipConfig config;
+
 
     @Autowired
     private RedisGbPlayMsgListener redisGbPlayMsgListener;
 
+    @Autowired
+    private VideoStreamSessionManager streamSession;
+
 
     @Override
     public void afterPropertiesSet() throws Exception {
@@ -168,7 +183,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
             // 查询请求是否来自上级平台\设备
             ParentPlatform platform = storager.queryParentPlatByServerGBId(requesterId);
             if (platform == null) {
-                inviteFromDeviceHandle(request, requesterId);
+                inviteFromDeviceHandle(request, requesterId, channelId);
 
             } else {
                 // 查询平台下是否有该通道
@@ -178,7 +193,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
 
                 MediaServerItem mediaServerItem = null;
                 StreamPushItem streamPushItem = null;
-                StreamProxyItem proxyByAppAndStream =null;
+                StreamProxyItem proxyByAppAndStream = null;
                 // 不是通道可能是直播流
                 if (channel != null && gbStream == null) {
                     // 通道存在,发100,TRYING
@@ -221,7 +236,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                                 }
                                 return;
                             }
-                        }else if("proxy".equals(gbStream.getStreamType())){
+                        } else if ("proxy".equals(gbStream.getStreamType())) {
                             proxyByAppAndStream = streamProxyService.getStreamProxyByAppAndStream(gbStream.getApp(), gbStream.getStream());
                             if (proxyByAppAndStream == null) {
                                 logger.info("[ app={}, stream={} ]找不到zlm {},返回410", gbStream.getApp(), gbStream.getStream(), mediaServerId);
@@ -353,10 +368,10 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                     if (mediaTransmissionTCP) {
                         if (tcpActive) {
                             streamTypeStr = "TCP-ACTIVE";
-                        }else {
+                        } else {
                             streamTypeStr = "TCP-PASSIVE";
                         }
-                    }else {
+                    } else {
                         streamTypeStr = "UDP";
                     }
                     logger.info("[上级Invite] {}, 平台:{}, 通道:{}, 收流地址:{}:{},收流方式:{}, ssrc:{}", sessionName, username, channelId, addressStr, port, streamTypeStr, ssrc);
@@ -440,7 +455,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                                 Map<String, Object> param = new HashMap<>(12);
                                 param.put("vhost","__defaultVhost__");
                                 param.put("app",sendRtpItem.getApp());
-                                param.put("stream",sendRtpItem.getStreamId());
+                                param.put("stream",sendRtpItem.getStream());
                                 param.put("ssrc", sendRtpItem.getSsrc());
                                 if (!sendRtpItem.isTcpActive()) {
                                     param.put("dst_url",sendRtpItem.getIp());
@@ -456,7 +471,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                                     // 开启rtcp保活
                                     param.put("udp_rtcp_timeout", sendRtpItem.isRtcp()? "1":"0");
                                 }
-                                JSONObject startSendRtpStreamResult = zlmServerFactory.startSendRtpStreamForPassive(mediaInfo, param);
+                                JSONObject startSendRtpStreamResult = zlmServerFactory.startSendRtpPassive(mediaInfo, param);
                                 if (startSendRtpStreamResult != null) {
                                     startSendRtpStreamHand(evt, sendRtpItem, null, startSendRtpStreamResult, param, callIdHeader);
                                 }
@@ -472,7 +487,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                                 Response response = getMessageFactory().createResponse(statusCode, evt.getRequest());
                                 sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
                             }
-                        } catch (ParseException | SipException  e) {
+                        } catch (ParseException | SipException e) {
                             logger.error("未处理的异常 ", e);
                         }
                     });
@@ -482,20 +497,19 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                         String startTimeStr = DateUtil.urlFormatter.format(start);
                         String endTimeStr = DateUtil.urlFormatter.format(end);
                         String stream = device.getDeviceId() + "_" + channelId + "_" + startTimeStr + "_" + endTimeStr;
-                        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, stream, null, device.isSsrcCheck(), true, 0, false, device.getStreamModeForParam());
-                        sendRtpItem.setStreamId(ssrcInfo.getStream());
+                        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, stream, null, device.isSsrcCheck(), true, 0,false, false, device.getStreamModeForParam());
                         // 写入redis, 超时时回复
                         redisCatchStorage.updateSendRTPSever(sendRtpItem);
                         playService.playBack(mediaServerItem, ssrcInfo, device.getDeviceId(), channelId, DateUtil.formatter.format(start),
                                 DateUtil.formatter.format(end),
                                 (code, msg, data) -> {
-                                    if (code == InviteErrorCode.SUCCESS.getCode()){
+                                    if (code == InviteErrorCode.SUCCESS.getCode()) {
                                         hookEvent.run(code, msg, data);
-                                    }else if (code == InviteErrorCode.ERROR_FOR_SIGNALLING_TIMEOUT.getCode() || code == InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode()){
+                                    } else if (code == InviteErrorCode.ERROR_FOR_SIGNALLING_TIMEOUT.getCode() || code == InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode()) {
                                         logger.info("[录像回放]超时, 用户:{}, 通道:{}", username, channelId);
                                         redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), channelId, callIdHeader.getCallId(), null);
                                         errorEvent.run(code, msg, data);
-                                    }else {
+                                    } else {
                                         errorEvent.run(code, msg, data);
                                     }
                                 });
@@ -512,8 +526,8 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                         }
 
                         sendRtpItem.setPlayType(InviteStreamType.DOWNLOAD);
-                        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, null, null, device.isSsrcCheck(), true, 0, false, device.getStreamModeForParam());
-                        sendRtpItem.setStreamId(ssrcInfo.getStream());
+                        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, null, null, device.isSsrcCheck(), true, 0, false, false, device.getStreamModeForParam());
+                        sendRtpItem.setStream(ssrcInfo.getStream());
                         // 写入redis, 超时时回复
                         redisCatchStorage.updateSendRTPSever(sendRtpItem);
                         playService.download(mediaServerItem, ssrcInfo, device.getDeviceId(), channelId, DateUtil.formatter.format(start),
@@ -532,7 +546,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                     } else {
                         sendRtpItem.setPlayType(InviteStreamType.PLAY);
                         String streamId = String.format("%s_%s", device.getDeviceId(), channelId);
-                        sendRtpItem.setStreamId(streamId);
+                        sendRtpItem.setStream(streamId);
                         redisCatchStorage.updateSendRTPSever(sendRtpItem);
                         SSRCInfo ssrcInfo = playService.play(mediaServerItem, device.getDeviceId(), channelId, ssrc, ((code, msg, data) -> {
                             if (code == InviteErrorCode.SUCCESS.getCode()) {
@@ -559,24 +573,24 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                         ssrc = gb28181Sdp.getSsrc();
                     }
 
-                    if("push".equals(gbStream.getStreamType())) {
+                    if ("push".equals(gbStream.getStreamType())) {
                         if (streamPushItem != null && streamPushItem.isPushIng()) {
                             // 推流状态
                             pushStream(evt, request, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
                                     mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
                         } else {
                             // 未推流 拉起
-                            notifyStreamOnline(evt, request,gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
+                            notifyStreamOnline(evt, request, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
                                     mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
                         }
-                    }else if ("proxy".equals(gbStream.getStreamType())){
+                    } else if ("proxy".equals(gbStream.getStreamType())) {
                         if (null != proxyByAppAndStream) {
-                            if(proxyByAppAndStream.isStatus()){
-                                pushProxyStream(evt, request, gbStream,  platform, callIdHeader, mediaServerItem, port, tcpActive,
+                            if (proxyByAppAndStream.isStatus()) {
+                                pushProxyStream(evt, request, gbStream, platform, callIdHeader, mediaServerItem, port, tcpActive,
                                         mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
-                            }else{
+                            } else {
                                 //开启代理拉流
-                                notifyStreamOnline(evt, request,gbStream, null, platform, callIdHeader, mediaServerItem, port, tcpActive,
+                                notifyStreamOnline(evt, request, gbStream, null, platform, callIdHeader, mediaServerItem, port, tcpActive,
                                         mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
                             }
                         }
@@ -617,33 +631,34 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                 SendRtpItem sendRtpItem = zlmServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
                         gbStream.getApp(), gbStream.getStream(), channelId, mediaTransmissionTCP, platform.isRtcp());
 
-                if (sendRtpItem == null) {
-                    logger.warn("服务器端口资源不足");
-                    try {
-                        responseAck(request, Response.BUSY_HERE);
-                    } catch (SipException | InvalidArgumentException | ParseException e) {
-                        logger.error("[命令发送失败] invite 服务器端口资源不足: {}", e.getMessage());
-                    }
-                    return;
-                }
-                if (tcpActive != null) {
-                    sendRtpItem.setTcpActive(tcpActive);
-                }
-                sendRtpItem.setPlayType(InviteStreamType.PUSH);
-                // 写入redis, 超时时回复
-                sendRtpItem.setStatus(1);
-                sendRtpItem.setCallId(callIdHeader.getCallId());
-                sendRtpItem.setFromTag(request.getFromTag());
-
-                SIPResponse response = sendStreamAck(mediaServerItem, request, sendRtpItem, platform, evt);
-                if (response != null) {
-                    sendRtpItem.setToTag(response.getToTag());
+            if (sendRtpItem == null) {
+                logger.warn("服务器端口资源不足");
+                try {
+                    responseAck(request, Response.BUSY_HERE);
+                } catch (SipException | InvalidArgumentException | ParseException e) {
+                    logger.error("[命令发送失败] invite 服务器端口资源不足: {}", e.getMessage());
                 }
-                redisCatchStorage.updateSendRTPSever(sendRtpItem);
+                return;
+            }
+            if (tcpActive != null) {
+                sendRtpItem.setTcpActive(tcpActive);
+            }
+            sendRtpItem.setPlayType(InviteStreamType.PUSH);
+            // 写入redis, 超时时回复
+            sendRtpItem.setStatus(1);
+            sendRtpItem.setCallId(callIdHeader.getCallId());
+            sendRtpItem.setFromTag(request.getFromTag());
+
+            SIPResponse response = sendStreamAck(mediaServerItem, request, sendRtpItem, platform, evt);
+            if (response != null) {
+                sendRtpItem.setToTag(response.getToTag());
+            }
+            redisCatchStorage.updateSendRTPSever(sendRtpItem);
 
         }
 
     }
+
     private void pushStream(RequestEvent evt, SIPRequest request, GbStream gbStream, StreamPushItem streamPushItem, ParentPlatform platform,
                             CallIdHeader callIdHeader, MediaServerItem mediaServerItem,
                             int port, Boolean tcpActive, boolean mediaTransmissionTCP,
@@ -678,12 +693,11 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                 if (response != null) {
                     sendRtpItem.setToTag(response.getToTag());
                 }
-
                 redisCatchStorage.updateSendRTPSever(sendRtpItem);
 
             } else {
                 // 不在线 拉起
-                notifyStreamOnline(evt, request,gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
+                notifyStreamOnline(evt, request, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
                         mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
             }
 
@@ -693,6 +707,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                     mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
         }
     }
+
     /**
      * 通知流上线
      */
@@ -709,7 +724,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                 OnStreamChangedHookParam streamChangedHookParam = (OnStreamChangedHookParam)hookParam;
                 logger.info("[上级点播]拉流代理已经就绪, {}/{}", streamChangedHookParam.getApp(), streamChangedHookParam.getStream());
                 dynamicTask.stop(callIdHeader.getCallId());
-                pushProxyStream(evt, request, gbStream,  platform, callIdHeader, mediaServerItem, port, tcpActive,
+                pushProxyStream(evt, request, gbStream, platform, callIdHeader, mediaServerItem, port, tcpActive,
                         mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
             });
             dynamicTask.startDelay(callIdHeader.getCallId(), () -> {
@@ -852,7 +867,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                     sendRtpItem.setCallId(callIdHeader.getCallId());
 
                     sendRtpItem.setFromTag(request.getFromTag());
-                    SIPResponse response = sendStreamAck(responseSendItemMsg.getMediaServerItem(), request,sendRtpItem, platform, evt);
+                    SIPResponse response = sendStreamAck(responseSendItemMsg.getMediaServerItem(), request, sendRtpItem, platform, evt);
                     if (response != null) {
                         sendRtpItem.setToTag(response.getToTag());
                     }
@@ -893,8 +908,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
         content.append("t=0 0\r\n");
         // 非严格模式端口不统一, 增加兼容性,修改为一个不为0的端口
         int localPort = sendRtpItem.getLocalPort();
-        if(localPort == 0)
-        {
+        if (localPort == 0) {
             localPort = new Random().nextInt(65535) + 1;
         }
         content.append("m=video " + localPort + " RTP/AVP 96\r\n");
@@ -913,39 +927,75 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
 
         try {
             return responseSdpAck(request, content.toString(), platform);
-        } catch (SipException e) {
-            logger.error("未处理的异常 ", e);
-        } catch (InvalidArgumentException e) {
-            logger.error("未处理的异常 ", e);
-        } catch (ParseException e) {
+        } catch (SipException | InvalidArgumentException | ParseException e) {
             logger.error("未处理的异常 ", e);
         }
         return null;
     }
 
-    public void inviteFromDeviceHandle(SIPRequest request, String requesterId) {
+    public void inviteFromDeviceHandle(SIPRequest request, String requesterId, String channelId) {
+
+        String realChannelId = null;
 
         // 非上级平台请求,查询是否设备请求(通常为接收语音广播的设备)
         Device device = redisCatchStorage.getDevice(requesterId);
+        // 判断requesterId是设备还是通道
+        if (device == null) {
+            device = storager.queryVideoDeviceByChannelId(requesterId);
+            realChannelId = requesterId;
+        }else {
+            realChannelId = channelId;
+        }
+        if (device == null) {
+            // 检查channelID是否可用
+            device = redisCatchStorage.getDevice(channelId);
+            if (device == null) {
+                device = storager.queryVideoDeviceByChannelId(channelId);
+                realChannelId = channelId;
+            }
+        }
+
+        if (device == null) {
+            logger.warn("来自设备的Invite请求,无法从请求信息中确定所属设备,已忽略,requesterId: {}/{}", requesterId, channelId);
+            try {
+                responseAck(request, Response.FORBIDDEN);
+            } catch (SipException | InvalidArgumentException | ParseException e) {
+                logger.error("[命令发送失败] 来自设备的Invite请求,无法从请求信息中确定所属设备 FORBIDDEN: {}", e.getMessage());
+            }
+            return;
+        }
+
+        AudioBroadcastCatch broadcastCatch = audioBroadcastManager.get(device.getDeviceId(), realChannelId);
+        if (broadcastCatch == null) {
+            logger.warn("来自设备的Invite请求非语音广播,已忽略,requesterId: {}/{}", requesterId, channelId);
+            try {
+                responseAck(request, Response.FORBIDDEN);
+            } catch (SipException | InvalidArgumentException | ParseException e) {
+                logger.error("[命令发送失败] 来自设备的Invite请求非语音广播 FORBIDDEN: {}", e.getMessage());
+            }
+            return;
+        }
         if (device != null) {
             logger.info("收到设备" + requesterId + "的语音广播Invite请求");
+            String key = VideoManagerConstants.BROADCAST_WAITE_INVITE + device.getDeviceId() + broadcastCatch.getChannelId();
+            dynamicTask.stop(key);
             try {
                 responseAck(request, Response.TRYING);
             } catch (SipException | InvalidArgumentException | ParseException e) {
                 logger.error("[命令发送失败] invite BAD_REQUEST: {}", e.getMessage());
+                playService.stopAudioBroadcast(device.getDeviceId(), broadcastCatch.getChannelId());
+                return;
             }
             String contentString = new String(request.getRawContent());
-            // jainSip不支持y=字段, 移除移除以解析。
-            String ssrc = "0000000404";
 
             try {
                 Gb28181Sdp gb28181Sdp = SipUtils.parseSDP(contentString);
                 SessionDescription sdp = gb28181Sdp.getBaseSdb();
                 //  获取支持的格式
                 Vector mediaDescriptions = sdp.getMediaDescriptions(true);
+
                 // 查看是否支持PS 负载96
                 int port = -1;
-                //boolean recvonly = false;
                 boolean mediaTransmissionTCP = false;
                 Boolean tcpActive = null;
                 for (int i = 0; i < mediaDescriptions.size(); i++) {
@@ -977,26 +1027,147 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                     try {
                         responseAck(request, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415
                     } catch (SipException | InvalidArgumentException | ParseException e) {
-                        logger.error("[命令发送失败] invite 不支持的媒体格式,返回415, {}", e.getMessage());
+                        logger.error("[命令发送失败] invite 不支持的媒体格式: {}", e.getMessage());
+                        playService.stopAudioBroadcast(device.getDeviceId(), broadcastCatch.getChannelId());
+                        return;
                     }
                     return;
                 }
-                String username = sdp.getOrigin().getUsername();
-                String addressStr = sdp.getConnection().getAddress();
-                logger.info("设备{}请求语音流,地址:{}:{},ssrc:{}", username, addressStr, port, ssrc);
-            } catch (SdpException e) {
-                logger.error("[SDP解析异常]", e);
-            }
+                String addressStr = sdp.getOrigin().getAddress();
+                logger.info("设备{}请求语音流,地址:{}:{},ssrc:{}, {}", requesterId, addressStr, port, gb28181Sdp.getSsrc(),
+                        mediaTransmissionTCP ? (tcpActive ? "TCP主动" : "TCP被动") : "UDP");
+
+                MediaServerItem mediaServerItem = broadcastCatch.getMediaServerItem();
+                if (mediaServerItem == null) {
+                    logger.warn("未找到语音喊话使用的zlm");
+                    try {
+                        responseAck(request, Response.BUSY_HERE);
+                    } catch (SipException | InvalidArgumentException | ParseException e) {
+                        logger.error("[命令发送失败] invite 未找到可用的zlm: {}", e.getMessage());
+                        playService.stopAudioBroadcast(device.getDeviceId(), broadcastCatch.getChannelId());
+                    }
+                    return;
+                }
+                logger.info("设备{}请求语音流, 收流地址:{}:{},ssrc:{}, {}, 对讲方式:{}", requesterId, addressStr, port, gb28181Sdp.getSsrc(),
+                        mediaTransmissionTCP ? (tcpActive ? "TCP主动" : "TCP被动") : "UDP", sdp.getSessionName().getValue());
+                CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME);
 
+                SendRtpItem sendRtpItem = zlmServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, gb28181Sdp.getSsrc(), requesterId,
+                        device.getDeviceId(), broadcastCatch.getChannelId(),
+                        mediaTransmissionTCP, false);
+
+                if (sendRtpItem == null) {
+                    logger.warn("服务器端口资源不足");
+                    try {
+                        responseAck(request, Response.BUSY_HERE);
+                    } catch (SipException | InvalidArgumentException | ParseException e) {
+                        logger.error("[命令发送失败] invite 服务器端口资源不足: {}", e.getMessage());
+                        playService.stopAudioBroadcast(device.getDeviceId(), broadcastCatch.getChannelId());
+                        return;
+                    }
+                    return;
+                }
 
 
+                sendRtpItem.setPlayType(InviteStreamType.BROADCAST);
+                sendRtpItem.setCallId(callIdHeader.getCallId());
+                sendRtpItem.setPlatformId(requesterId);
+                sendRtpItem.setStatus(1);
+                sendRtpItem.setApp(broadcastCatch.getApp());
+                sendRtpItem.setStream(broadcastCatch.getStream());
+                sendRtpItem.setPt(8);
+                sendRtpItem.setUsePs(false);
+                sendRtpItem.setRtcp(false);
+                sendRtpItem.setOnlyAudio(true);
+                sendRtpItem.setTcp(mediaTransmissionTCP);
+                if (tcpActive != null) {
+                    sendRtpItem.setTcpActive(tcpActive);
+                }
+
+                redisCatchStorage.updateSendRTPSever(sendRtpItem);
+
+                Boolean streamReady = zlmServerFactory.isStreamReady(mediaServerItem, broadcastCatch.getApp(), broadcastCatch.getStream());
+                if (streamReady) {
+                    sendOk(device, sendRtpItem, sdp, request, mediaServerItem, mediaTransmissionTCP, gb28181Sdp.getSsrc());
+                } else {
+                    logger.warn("[语音通话], 未发现待推送的流,app={},stream={}", broadcastCatch.getApp(), broadcastCatch.getStream());
+                    try {
+                        responseAck(request, Response.GONE);
+                    } catch (SipException | InvalidArgumentException | ParseException e) {
+                        logger.error("[命令发送失败] 语音通话 回复410失败, {}", e.getMessage());
+                        return;
+                    }
+                    playService.stopAudioBroadcast(device.getDeviceId(), broadcastCatch.getChannelId());
+                }
+            } catch (SdpException e) {
+                logger.error("[SDP解析异常]", e);
+                playService.stopAudioBroadcast(device.getDeviceId(), broadcastCatch.getChannelId());
+            }
         } else {
             logger.warn("来自无效设备/平台的请求");
             try {
-                responseAck(request, Response.BAD_REQUEST);; // 不支持的格式,发415
+                responseAck(request, Response.BAD_REQUEST);
+                ; // 不支持的格式,发415
             } catch (SipException | InvalidArgumentException | ParseException e) {
                 logger.error("[命令发送失败] invite 来自无效设备/平台的请求, {}", e.getMessage());
             }
         }
     }
+
+    SIPResponse sendOk(Device device, SendRtpItem sendRtpItem, SessionDescription sdp, SIPRequest request, MediaServerItem mediaServerItem, boolean mediaTransmissionTCP, String ssrc) {
+        SIPResponse sipResponse = null;
+        try {
+            sendRtpItem.setStatus(2);
+            redisCatchStorage.updateSendRTPSever(sendRtpItem);
+            StringBuffer content = new StringBuffer(200);
+            content.append("v=0\r\n");
+            content.append("o=" + config.getId() + " " + sdp.getOrigin().getSessionId() + " " + sdp.getOrigin().getSessionVersion() + " IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
+            content.append("s=Play\r\n");
+            content.append("c=IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
+            content.append("t=0 0\r\n");
+
+            if (mediaTransmissionTCP) {
+                content.append("m=audio " + sendRtpItem.getLocalPort() + " TCP/RTP/AVP 8\r\n");
+            } else {
+                content.append("m=audio " + sendRtpItem.getLocalPort() + " RTP/AVP 8\r\n");
+            }
+
+            content.append("a=rtpmap:8 PCMA/8000/1\r\n");
+
+            content.append("a=sendonly\r\n");
+            if (sendRtpItem.isTcp()) {
+                content.append("a=connection:new\r\n");
+                if (!sendRtpItem.isTcpActive()) {
+                    content.append("a=setup:active\r\n");
+                } else {
+                    content.append("a=setup:passive\r\n");
+                }
+            }
+            content.append("y=" + ssrc + "\r\n");
+            content.append("f=v/////a/1/8/1\r\n");
+
+            ParentPlatform parentPlatform = new ParentPlatform();
+            parentPlatform.setServerIP(device.getIp());
+            parentPlatform.setServerPort(device.getPort());
+            parentPlatform.setServerGBId(device.getDeviceId());
+
+            sipResponse = responseSdpAck(request, content.toString(), parentPlatform);
+
+            AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(device.getDeviceId(), sendRtpItem.getChannelId());
+
+            audioBroadcastCatch.setStatus(AudioBroadcastCatchStatus.Ok);
+            audioBroadcastCatch.setSipTransactionInfoByRequset(sipResponse);
+            audioBroadcastManager.update(audioBroadcastCatch);
+            streamSession.put(device.getDeviceId(), sendRtpItem.getChannelId(), request.getCallIdHeader().getCallId(), sendRtpItem.getStream(), sendRtpItem.getSsrc(), sendRtpItem.getMediaServerId(), sipResponse, InviteSessionType.BROADCAST);
+            // 开启发流,大华在收到200OK后就会开始建立连接
+            if (!device.isBroadcastPushAfterAck()) {
+                logger.info("[语音喊话] 回复200OK后发现 BroadcastPushAfterAck为False,现在开始推流");
+                playService.startPushStream(sendRtpItem, sipResponse, parentPlatform, request.getCallIdHeader());
+            }
+
+        } catch (SipException | InvalidArgumentException | ParseException | SdpParseException e) {
+            logger.error("[命令发送失败] 语音喊话 回复200OK(SDP): {}", e.getMessage());
+        }
+        return sipResponse;
+    }
 }

+ 3 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java

@@ -27,6 +27,9 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
 
+import javax.sip.*;
+import javax.sip.header.*;
+import javax.sip.message.Request;
 import javax.sip.RequestEvent;
 import javax.sip.SipException;
 import javax.sip.header.AuthorizationHeader;

+ 3 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java

@@ -1,5 +1,8 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
 
+import com.genersoft.iot.vmp.common.VideoManagerConstants;
+import com.genersoft.iot.vmp.conf.DynamicTask;
+import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.CmdType;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder;

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/info/InfoRequestProcessor.java

@@ -109,7 +109,7 @@ public class InfoRequestProcessor extends SIPRequestProcessorParent implements I
                 String contentSubType = header.getContentSubType();
                 if ("Application".equalsIgnoreCase(contentType) && "MANSRTSP".equalsIgnoreCase(contentSubType)) {
                     SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, null, callIdHeader.getCallId());
-                    String streamId = sendRtpItem.getStreamId();
+                    String streamId = sendRtpItem.getStream();
                     InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(InviteSessionType.PLAYBACK, streamId);
                     if (null == inviteInfo) {
                         responseAck(request, Response.NOT_FOUND, "stream " + streamId + " not found");

+ 17 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageHandlerAbstract.java

@@ -5,10 +5,17 @@ import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query.cmd.CatalogQueryMessageHandler;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
+import gov.nist.javax.sip.message.SIPRequest;
 import org.dom4j.Element;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 
+import javax.sip.InvalidArgumentException;
 import javax.sip.RequestEvent;
+import javax.sip.SipException;
+import javax.sip.message.Response;
+import java.text.ParseException;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -16,6 +23,8 @@ import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText;
 
 public abstract class MessageHandlerAbstract extends SIPRequestProcessorParent implements IMessageHandler{
 
+    private Logger logger = LoggerFactory.getLogger(MessageHandlerAbstract.class);
+
     public Map<String, IMessageHandler> messageHandlerMap = new ConcurrentHashMap<>();
 
     @Autowired
@@ -28,6 +37,14 @@ public abstract class MessageHandlerAbstract extends SIPRequestProcessorParent i
     @Override
     public void handForDevice(RequestEvent evt, Device device, Element element) {
         String cmd = getText(element, "CmdType");
+        if (cmd == null) {
+            try {
+                responseAck((SIPRequest) evt.getRequest(), Response.OK);
+            } catch (SipException | InvalidArgumentException | ParseException e) {
+                logger.error("[命令发送失败] 回复200 OK: {}", e.getMessage());
+            }
+            return;
+        }
         IMessageHandler messageHandler = messageHandlerMap.get(cmd);
 
         if (messageHandler != null) {

+ 197 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/BroadcastNotifyMessageHandler.java

@@ -0,0 +1,197 @@
+package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.cmd;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
+import com.genersoft.iot.vmp.gb28181.bean.*;
+import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
+import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
+import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
+import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
+import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.NotifyMessageHandler;
+import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam;
+import com.genersoft.iot.vmp.service.IDeviceService;
+import com.genersoft.iot.vmp.service.IMediaServerService;
+import com.genersoft.iot.vmp.service.IPlatformService;
+import com.genersoft.iot.vmp.service.IPlayService;
+import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
+import gov.nist.javax.sip.message.SIPRequest;
+import org.dom4j.Element;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.sip.InvalidArgumentException;
+import javax.sip.RequestEvent;
+import javax.sip.SipException;
+import javax.sip.message.Response;
+import java.text.ParseException;
+
+/**
+ * 状态信息(心跳)报送
+ */
+@Component
+public class BroadcastNotifyMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
+
+    private Logger logger = LoggerFactory.getLogger(BroadcastNotifyMessageHandler.class);
+    private final static String cmdType = "Broadcast";
+
+    @Autowired
+    private NotifyMessageHandler notifyMessageHandler;
+
+    @Autowired
+    private IVideoManagerStorage storage;
+
+    @Autowired
+    private ISIPCommanderForPlatform commanderForPlatform;
+
+    @Autowired
+    private IMediaServerService mediaServerService;
+
+    @Autowired
+    private IPlayService playService;
+
+    @Autowired
+    private IDeviceService deviceService;
+
+    @Autowired
+    private IPlatformService platformService;
+
+    @Autowired
+    private AudioBroadcastManager audioBroadcastManager;
+
+    @Autowired
+    private ZLMServerFactory zlmServerFactory;
+
+    @Autowired
+    private IRedisCatchStorage redisCatchStorage;
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        notifyMessageHandler.addHandler(cmdType, this);
+    }
+
+    @Override
+    public void handForDevice(RequestEvent evt, Device device, Element element) {
+
+    }
+
+    @Override
+    public void handForPlatform(RequestEvent evt, ParentPlatform platform, Element rootElement) {
+        // 来自上级平台的语音喊话请求
+        SIPRequest request = (SIPRequest) evt.getRequest();
+        try {
+            Element snElement = rootElement.element("SN");
+            if (snElement == null) {
+                responseAck(request, Response.BAD_REQUEST, "sn must not null");
+                return;
+            }
+            String sn = snElement.getText();
+            Element targetIDElement = rootElement.element("TargetID");
+            if (targetIDElement == null) {
+                responseAck(request, Response.BAD_REQUEST, "TargetID must not null");
+                return;
+            }
+            String targetId = targetIDElement.getText();
+
+
+            logger.info("[国标级联 语音喊话] platform: {}, channel: {}", platform.getServerGBId(), targetId);
+
+            DeviceChannel deviceChannel = storage.queryChannelInParentPlatform(platform.getServerGBId(), targetId);
+            if (deviceChannel == null) {
+                logger.warn("[国标级联 语音喊话] 未找到通道 platform: {}, channel: {}", platform.getServerGBId(), targetId);
+                responseAck(request, Response.NOT_FOUND, "TargetID not found");
+                return;
+            }
+            // 向下级发送语音的喊话请求
+            Device device = deviceService.getDevice(deviceChannel.getDeviceId());
+            if (device == null) {
+                responseAck(request, Response.NOT_FOUND, "device not found");
+                return;
+            }
+            responseAck(request, Response.OK);
+
+            // 查看语音通道是否已经建立并且已经在使用
+            if (playService.audioBroadcastInUse(device, targetId)) {
+                commanderForPlatform.broadcastResultCmd(platform, deviceChannel, sn, false,null, null);
+                return;
+            }
+
+            MediaServerItem mediaServerForMinimumLoad = mediaServerService.getMediaServerForMinimumLoad(null);
+            commanderForPlatform.broadcastResultCmd(platform, deviceChannel, sn, true,  eventResult->{
+                logger.info("[国标级联] 语音喊话 回复失败 platform: {}, 错误:{}/{}", platform.getServerGBId(), eventResult.statusCode, eventResult.msg);
+            }, eventResult->{
+
+                // 消息发送成功, 向上级发送invite,获取推流
+                try {
+                    platformService.broadcastInvite(platform, deviceChannel.getChannelId(), mediaServerForMinimumLoad,  (mediaServerItem, hookParam)->{
+                        OnStreamChangedHookParam streamChangedHookParam = (OnStreamChangedHookParam)hookParam;
+                        // 上级平台推流成功
+                        AudioBroadcastCatch broadcastCatch = audioBroadcastManager.get(device.getDeviceId(), targetId);
+                        if (broadcastCatch != null ) {
+                            if (playService.audioBroadcastInUse(device, targetId)) {
+                                logger.info("[国标级联] 语音喊话 设备正在使用中 platform: {}, channel: {}",
+                                        platform.getServerGBId(), deviceChannel.getChannelId());
+                                //  查看语音通道已经建立且已经占用 回复BYE
+                                platformService.stopBroadcast(platform, deviceChannel, streamChangedHookParam.getStream(),  true, mediaServerItem);
+                            }else {
+                                // 查看语音通道已经建立但是未占用
+                                broadcastCatch.setApp(streamChangedHookParam.getApp());
+                                broadcastCatch.setStream(streamChangedHookParam.getStream());
+                                broadcastCatch.setMediaServerItem(mediaServerItem);
+                                audioBroadcastManager.update(broadcastCatch);
+                                // 推流到设备
+                                SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, targetId, streamChangedHookParam.getStream(), null);
+                                if (sendRtpItem == null) {
+                                    logger.warn("[国标级联] 语音喊话 异常,未找到发流信息, channelId: {}, stream: {}", targetId, streamChangedHookParam.getStream());
+                                    logger.info("[国标级联] 语音喊话 重新开始,channelId: {}, stream: {}", targetId, streamChangedHookParam.getStream());
+                                    try {
+                                        playService.audioBroadcastCmd(device, targetId, mediaServerItem, streamChangedHookParam.getApp(), streamChangedHookParam.getStream(), 60, true, msg -> {
+                                            logger.info("[语音喊话] 通道建立成功, device: {}, channel: {}", device.getDeviceId(), targetId);
+                                        });
+                                    } catch (SipException | InvalidArgumentException | ParseException e) {
+                                        logger.info("[消息发送失败] 国标级联 语音喊话 platform: {}", platform.getServerGBId());
+                                    }
+                                }else {
+                                    // 发流
+                                    JSONObject jsonObject = zlmServerFactory.startSendRtp(mediaServerItem, sendRtpItem);
+                                    if (jsonObject != null && jsonObject.getInteger("code") == 0 ) {
+                                        logger.info("[语音喊话] 自动推流成功, device: {}, channel: {}", device.getDeviceId(), targetId);
+                                    }else {
+                                        logger.info("[语音喊话] 推流失败, 结果: {}", jsonObject);
+                                    }
+                                }
+                            }
+                        }else {
+                            try {
+                                playService.audioBroadcastCmd(device, targetId, mediaServerItem, streamChangedHookParam.getApp(), streamChangedHookParam.getStream(), 60, true, msg -> {
+                                    logger.info("[语音喊话] 通道建立成功, device: {}, channel: {}", device.getDeviceId(), targetId);
+                                });
+                            } catch (SipException | InvalidArgumentException | ParseException e) {
+                                logger.info("[消息发送失败] 国标级联 语音喊话 platform: {}", platform.getServerGBId());
+                            }
+                        }
+
+                    }, eventResultForBroadcastInvite -> {
+                        // 收到错误
+                        logger.info("[国标级联-语音喊话] 与下级通道建立失败 device: {}, channel: {}, 错误:{}/{}", device.getDeviceId(),
+                                targetId, eventResultForBroadcastInvite.statusCode, eventResultForBroadcastInvite.msg);
+                    }, (code, msg)->{
+                        // 超时
+                        logger.info("[国标级联-语音喊话] 与下级通道建立超时 device: {}, channel: {}, 错误:{}/{}", device.getDeviceId(),
+                                targetId, code, msg);
+                    });
+                } catch (SipException | InvalidArgumentException | ParseException e) {
+                    logger.info("[消息发送失败] 国标级联 语音喊话 invite消息 platform: {}", platform.getServerGBId());
+                }
+            });
+        } catch (SipException | InvalidArgumentException | ParseException e) {
+            logger.info("[消息发送失败] 国标级联 语音喊话 platform: {}", platform.getServerGBId());
+        }
+
+    }
+}

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java

@@ -102,7 +102,7 @@ public class MediaStatusNotifyMessageHandler extends SIPRequestProcessorParent i
 
                 try {
                     cmder.streamByeCmd(device, ssrcTransaction.getChannelId(), null, callIdHeader.getCallId());
-                } catch (InvalidArgumentException | ParseException | SsrcTransactionNotFoundException | SipException e) {
+                } catch (InvalidArgumentException | ParseException  | SipException | SsrcTransactionNotFoundException e) {
                     logger.error("[录像流]推送完毕,收到关流通知, 发送BYE失败 {}", e.getMessage());
                 }
                 // 去除监听流注销自动停止下载的监听

+ 41 - 17
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/BroadcastResponseMessageHandler.java

@@ -1,14 +1,17 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd;
 
-import com.alibaba.fastjson2.JSONObject;
+import com.genersoft.iot.vmp.common.VideoManagerConstants;
+import com.genersoft.iot.vmp.conf.DynamicTask;
+import com.genersoft.iot.vmp.gb28181.bean.AudioBroadcastCatch;
+import com.genersoft.iot.vmp.gb28181.bean.AudioBroadcastCatchStatus;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
+import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
-import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler;
-import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
+import com.genersoft.iot.vmp.service.IPlayService;
 import gov.nist.javax.sip.message.SIPRequest;
 import org.dom4j.Element;
 import org.slf4j.Logger;
@@ -35,7 +38,13 @@ public class BroadcastResponseMessageHandler extends SIPRequestProcessorParent i
     private ResponseMessageHandler responseMessageHandler;
 
     @Autowired
-    private DeferredResultHolder deferredResultHolder;
+    private DynamicTask dynamicTask;
+
+    @Autowired
+    private AudioBroadcastManager audioBroadcastManager;
+
+    @Autowired
+    private IPlayService playService;
 
     @Override
     public void afterPropertiesSet() throws Exception {
@@ -44,23 +53,38 @@ public class BroadcastResponseMessageHandler extends SIPRequestProcessorParent i
 
     @Override
     public void handForDevice(RequestEvent evt, Device device, Element rootElement) {
+
+        SIPRequest request = (SIPRequest) evt.getRequest();
         try {
             String channelId = getText(rootElement, "DeviceID");
-            String key = DeferredResultHolder.CALLBACK_CMD_BROADCAST + device.getDeviceId() + channelId;
-            // 回复200 OK
-            responseAck((SIPRequest) evt.getRequest(), Response.OK);
-            // 此处是对本平台发出Broadcast指令的应答
-            JSONObject json = new JSONObject();
-            XmlUtil.node2Json(rootElement, json);
-            if (logger.isDebugEnabled()) {
-                logger.debug(json.toJSONString());
+            if (!audioBroadcastManager.exit(device.getDeviceId(), channelId)) {
+                // 回复410
+                responseAck((SIPRequest) evt.getRequest(), Response.GONE);
+                return;
             }
-            RequestMessage msg = new RequestMessage();
-            msg.setKey(key);
-            msg.setData(json);
-            deferredResultHolder.invokeAllResult(msg);
-
+            String result = getText(rootElement, "Result");
+            Element infoElement = rootElement.element("Info");
+            String reason = null;
+            if (infoElement != null) {
+                reason = getText(infoElement, "Reason");
+            }
+            logger.info("[语音广播]回复:{}, {}/{}", reason == null? result : result + ": " + reason, device.getDeviceId(), channelId );
 
+            // 回复200 OK
+            responseAck(request, Response.OK);
+            if (result.equalsIgnoreCase("OK")) {
+                AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(device.getDeviceId(), channelId);
+                audioBroadcastCatch.setStatus(AudioBroadcastCatchStatus.WaiteInvite);
+                audioBroadcastManager.update(audioBroadcastCatch);
+                // 等待invite消息, 超时则结束
+                String key = VideoManagerConstants.BROADCAST_WAITE_INVITE +  device.getDeviceId() + channelId;
+                dynamicTask.startDelay(key, ()->{
+                    logger.info("[语音广播]等待invite消息超时:{}/{}", device.getDeviceId(), channelId);
+                    playService.stopAudioBroadcast(device.getDeviceId(), channelId);
+                }, 2000);
+            }else {
+                playService.stopAudioBroadcast(device.getDeviceId(), channelId);
+            }
         } catch (ParseException | SipException | InvalidArgumentException e) {
             logger.error("[命令发送失败] 国标级联 语音喊话: {}", e.getMessage());
         }

+ 1 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java

@@ -39,6 +39,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
 public class CatalogResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
 
     private Logger logger = LoggerFactory.getLogger(CatalogResponseMessageHandler.class);
+
     private final String cmdType = "Catalog";
 
     @Autowired

+ 3 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java

@@ -19,6 +19,9 @@ import javax.sdp.SessionDescription;
 import javax.sip.InvalidArgumentException;
 import javax.sip.ResponseEvent;
 import javax.sip.SipException;
+import javax.sip.InvalidArgumentException;
+import javax.sip.ResponseEvent;
+import javax.sip.SipException;
 import javax.sip.SipFactory;
 import javax.sip.address.SipURI;
 import javax.sip.message.Request;

+ 20 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java

@@ -140,6 +140,26 @@ public class SipUtils {
         return builder.toString();
     }
 
+    public static String getNewCallId() {
+        return (int) Math.floor(Math.random() * 1000000000) + "";
+    }
+
+    public static int getTypeCodeFromGbCode(String deviceId) {
+        if (ObjectUtils.isEmpty(deviceId)) {
+            return 0;
+        }
+        return Integer.parseInt(deviceId.substring(10, 13));
+    }
+
+    /**
+     * 判断是否是前端外围设备
+     * @param deviceId
+     * @return
+     */
+    public static boolean isFrontEnd(String deviceId) {
+        int typeCodeFromGbCode = getTypeCodeFromGbCode(deviceId);
+        return typeCodeFromGbCode > 130 && typeCodeFromGbCode < 199;
+    }
     /**
      * 从请求中获取设备ip地址和端口号
      * @param request 请求

+ 2 - 2
src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java

@@ -38,7 +38,7 @@ public class AssistRESTfulUtils {
     private OkHttpClient getClient(){
         return getClient(null);
     }
-    
+
     private OkHttpClient getClient(Integer readTimeOut){
         if (client == null) {
             if (readTimeOut == null) {
@@ -251,7 +251,7 @@ public class AssistRESTfulUtils {
 
     public JSONObject addTask(MediaServerItem mediaServerItem, String app, String stream, String startTime,
                               String endTime, String callId, List<String> filePathList, String remoteHost) {
-        
+
         JSONObject videoTaskInfoJSON = new JSONObject();
         videoTaskInfoJSON.put("app", app);
         videoTaskInfoJSON.put("stream", stream);

ファイルの差分が大きいため隠しています
+ 906 - 820
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java


+ 9 - 2
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java

@@ -96,6 +96,7 @@ public class ZLMRESTfulUtils {
             if (callback == null) {
                 try {
                     Response response = client.newCall(request).execute();
+
                     if (response.isSuccessful()) {
                         ResponseBody responseBody = response.body();
                         if (responseBody != null) {
@@ -103,6 +104,8 @@ public class ZLMRESTfulUtils {
                             responseJSON = JSON.parseObject(responseStr);
                         }
                     }else {
+                        System.out.println( 2222);
+                        System.out.println( response.code());
                         response.close();
                         Objects.requireNonNull(response.body()).close();
                     }
@@ -111,11 +114,11 @@ public class ZLMRESTfulUtils {
 
                     if(e instanceof SocketTimeoutException){
                         //读取超时超时异常
-                        logger.error(String.format("读取ZLM数据失败: %s, %s", url, e.getMessage()));
+                        logger.error(String.format("读取ZLM数据超时失败: %s, %s", url, e.getMessage()));
                     }
                     if(e instanceof ConnectException){
                         //判断连接异常,我这里是报Failed to connect to 10.7.5.144
-                        logger.error(String.format("连接ZLM失败: %s, %s", url, e.getMessage()));
+                        logger.error(String.format("连接ZLM连接失败: %s, %s", url, e.getMessage()));
                     }
 
                 }catch (Exception e){
@@ -323,6 +326,10 @@ public class ZLMRESTfulUtils {
         return sendPost(mediaServerItem, "startSendRtpPassive",param, null);
     }
 
+    public JSONObject startSendRtpPassive(MediaServerItem mediaServerItem, Map<String, Object> param, RequestCallback callback) {
+        return sendPost(mediaServerItem, "startSendRtpPassive",param, callback);
+    }
+
     public JSONObject stopSendRtp(MediaServerItem mediaServerItem, Map<String, Object> param) {
         return sendPost(mediaServerItem, "stopSendRtp",param, null);
     }

+ 58 - 9
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerFactory.java

@@ -42,7 +42,7 @@ public class ZLMServerFactory {
      * @param tcpMode 0/null udp 模式,1 tcp 被动模式, 2 tcp 主动模式。
      * @return
      */
-    public int createRTPServer(MediaServerItem mediaServerItem, String streamId, long ssrc, Integer port, Boolean reUsePort, Integer tcpMode) {
+    public int createRTPServer(MediaServerItem mediaServerItem, String streamId, long ssrc, Integer port, Boolean onlyAuto, Boolean reUsePort, Integer tcpMode) {
         int result = -1;
         // 查询此rtp server 是否已经存在
         JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerItem, streamId);
@@ -58,7 +58,7 @@ public class ZLMServerFactory {
                     JSONObject jsonObject = zlmresTfulUtils.closeRtpServer(mediaServerItem, param);
                     if (jsonObject != null ) {
                         if (jsonObject.getInteger("code") == 0) {
-                            return createRTPServer(mediaServerItem, streamId, ssrc, port, reUsePort, tcpMode);
+                            return createRTPServer(mediaServerItem, streamId, ssrc, port,onlyAuto, reUsePort, tcpMode);
                         }else {
                             logger.warn("[开启rtpServer], 重启RtpServer错误");
                         }
@@ -86,6 +86,9 @@ public class ZLMServerFactory {
         }else {
             param.put("port", port);
         }
+        if (onlyAuto != null) {
+            param.put("only_audio", onlyAuto?"1":"0");
+        }
         if (ssrc != 0) {
             param.put("ssrc", ssrc);
         }
@@ -111,9 +114,10 @@ public class ZLMServerFactory {
             Map<String, Object> param = new HashMap<>();
             param.put("stream_id", streamId);
             JSONObject jsonObject = zlmresTfulUtils.closeRtpServer(serverItem, param);
+            logger.info("关闭RTP Server " +  jsonObject);
             if (jsonObject != null ) {
                 if (jsonObject.getInteger("code") == 0) {
-                    result = jsonObject.getInteger("hit") == 1;
+                    result = jsonObject.getInteger("hit") >= 1;
                 }else {
                     logger.error("关闭RTP Server 失败: " + jsonObject.getString("msg"));
                 }
@@ -206,7 +210,7 @@ public class ZLMServerFactory {
         sendRtpItem.setPort(port);
         sendRtpItem.setSsrc(ssrc);
         sendRtpItem.setApp(app);
-        sendRtpItem.setStreamId(stream);
+        sendRtpItem.setStream(stream);
         sendRtpItem.setPlatformId(platformId);
         sendRtpItem.setChannelId(channelId);
         sendRtpItem.setTcp(tcp);
@@ -227,10 +231,14 @@ public class ZLMServerFactory {
     /**
      * 调用zlm RESTFUL API —— startSendRtpPassive
      */
-    public JSONObject startSendRtpStreamForPassive(MediaServerItem mediaServerItem, Map<String, Object>param) {
+    public JSONObject startSendRtpPassive(MediaServerItem mediaServerItem, Map<String, Object>param) {
         return zlmresTfulUtils.startSendRtpPassive(mediaServerItem, param);
     }
 
+    public JSONObject startSendRtpPassive(MediaServerItem mediaServerItem, Map<String, Object>param, ZLMRESTfulUtils.RequestCallback callback) {
+        return zlmresTfulUtils.startSendRtpPassive(mediaServerItem, param, callback);
+    }
+
     /**
      * 查询待转推的流是否就绪
      */
@@ -266,11 +274,11 @@ public class ZLMServerFactory {
             return 0;
         }
         Integer code = mediaInfo.getInteger("code");
-        if ( code < 0) {
+        if (code < 0) {
             logger.warn("查询流({}/{})是否有其它观看者时得到: {}", app, streamId, mediaInfo.getString("msg"));
             return -1;
         }
-        if ( code == 0 && mediaInfo.getBoolean("online") != null && !mediaInfo.getBoolean("online")) {
+        if ( code == 0 && mediaInfo.getBoolean("online") != null && ! mediaInfo.getBoolean("online")) {
             logger.warn("查询流({}/{})是否有其它观看者时得到: {}", app, streamId, mediaInfo.getString("msg"));
             return -1;
         }
@@ -289,13 +297,54 @@ public class ZLMServerFactory {
             result= true;
             logger.info("[停止RTP推流] 成功");
         } else {
-            logger.error("[停止RTP推流] 失败: {}, 参数:{}->\r\n{}",jsonObject.getString("msg"), JSON.toJSON(param), jsonObject);
+            logger.warn("[停止RTP推流] 失败: {}, 参数:{}->\r\n{}",jsonObject.getString("msg"), JSON.toJSON(param), jsonObject);
         }
         return result;
     }
 
-    public void closeAllSendRtpStream() {
+    public JSONObject startSendRtp(MediaServerItem mediaInfo, SendRtpItem sendRtpItem) {
+        String is_Udp = sendRtpItem.isTcp() ? "0" : "1";
+        logger.info("rtp/{}开始推流, 目标={}:{},SSRC={}", sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc());
+        Map<String, Object> param = new HashMap<>(12);
+        param.put("vhost","__defaultVhost__");
+        param.put("app",sendRtpItem.getApp());
+        param.put("stream",sendRtpItem.getStream());
+        param.put("ssrc", sendRtpItem.getSsrc());
+        param.put("src_port", sendRtpItem.getLocalPort());
+        param.put("pt", sendRtpItem.getPt());
+        param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0");
+        param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0");
+        if (!sendRtpItem.isTcp()) {
+            // udp模式下开启rtcp保活
+            param.put("udp_rtcp_timeout", sendRtpItem.isRtcp()? "1":"0");
+        }
 
+        if (mediaInfo == null) {
+            return null;
+        }
+        // 如果是非严格模式,需要关闭端口占用
+        JSONObject startSendRtpStreamResult = null;
+        if (sendRtpItem.getLocalPort() != 0) {
+            if (sendRtpItem.isTcpActive()) {
+                startSendRtpStreamResult = startSendRtpPassive(mediaInfo, param);
+                System.out.println(JSON.toJSON(param));
+            }else {
+                param.put("is_udp", is_Udp);
+                param.put("dst_url", sendRtpItem.getIp());
+                param.put("dst_port", sendRtpItem.getPort());
+                startSendRtpStreamResult = startSendRtpStream(mediaInfo, param);
+            }
+        }else {
+            if (sendRtpItem.isTcpActive()) {
+                startSendRtpStreamResult = startSendRtpPassive(mediaInfo, param);
+            }else {
+                param.put("is_udp", is_Udp);
+                param.put("dst_url", sendRtpItem.getIp());
+                param.put("dst_port", sendRtpItem.getPort());
+                startSendRtpStreamResult = startSendRtpStream(mediaInfo, param);
+            }
+        }
+        return startSendRtpStreamResult;
     }
 
     public Boolean updateRtpServerSSRC(MediaServerItem mediaServerItem, String streamId, String ssrc) {

+ 2 - 0
src/main/java/com/genersoft/iot/vmp/media/zlm/ZlmHttpHookSubscribe.java

@@ -39,6 +39,7 @@ public class ZlmHttpHookSubscribe {
             hookSubscribe.setExpires(expiresInstant);
         }
         allSubscribes.computeIfAbsent(hookSubscribe.getHookType(), k -> new ConcurrentHashMap<>()).put(hookSubscribe, event);
+        System.out.println(allSubscribes);
     }
 
     public ZlmHttpHookSubscribe.Event sendNotify(HookType type, JSONObject hookResponse) {
@@ -49,6 +50,7 @@ public class ZlmHttpHookSubscribe {
         }
         for (IHookSubscribe key : eventMap.keySet()) {
             Boolean result = null;
+
             for (String s : key.getContent().keySet()) {
                 if (result == null) {
                     result = key.getContent().getString(s).equals(hookResponse.getString(s));

+ 16 - 0
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeFactory.java

@@ -35,6 +35,21 @@ public class HookSubscribeFactory {
         return hookSubscribe;
     }
 
+    public static HookSubscribeForStreamPush on_publish(String app, String stream, String scheam, String mediaServerId) {
+        HookSubscribeForStreamPush hookSubscribe = new HookSubscribeForStreamPush();
+        JSONObject subscribeKey = new JSONObject();
+        subscribeKey.put("app", app);
+        subscribeKey.put("stream", stream);
+        if (scheam != null) {
+            subscribeKey.put("schema", scheam);
+        }
+        subscribeKey.put("mediaServerId", mediaServerId);
+        hookSubscribe.setContent(subscribeKey);
+
+        return hookSubscribe;
+    }
+
+
     public static HookSubscribeForServerStarted on_server_started() {
         HookSubscribeForServerStarted hookSubscribe = new HookSubscribeForServerStarted();
         hookSubscribe.setContent(new JSONObject());
@@ -52,4 +67,5 @@ public class HookSubscribeFactory {
 
         return hookSubscribe;
     }
+
 }

+ 43 - 0
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeForStreamPush.java

@@ -0,0 +1,43 @@
+package com.genersoft.iot.vmp.media.zlm.dto;
+
+
+import com.alibaba.fastjson2.JSONObject;
+
+import java.time.Instant;
+
+/**
+ * hook订阅-开始推流
+ * @author lin
+ */
+public class HookSubscribeForStreamPush implements IHookSubscribe{
+
+    private HookType hookType = HookType.on_publish;
+
+    private JSONObject content;
+
+    private Instant expires;
+
+    @Override
+    public HookType getHookType() {
+        return hookType;
+    }
+
+    @Override
+    public JSONObject getContent() {
+        return content;
+    }
+
+    public void setContent(JSONObject content) {
+        this.content = content;
+    }
+
+    @Override
+    public Instant getExpires() {
+        return expires;
+    }
+
+    @Override
+    public void setExpires(Instant expires) {
+        this.expires = expires;
+    }
+}

+ 168 - 0
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaServerItemLite.java

@@ -0,0 +1,168 @@
+package com.genersoft.iot.vmp.media.zlm.dto;
+
+
+/**
+ * 精简的MediaServerItem信息,方便给前端返回数据
+ */
+public class MediaServerItemLite {
+
+    private String id;
+
+    private String ip;
+
+    private String hookIp;
+
+    private String sdpIp;
+
+    private String streamIp;
+
+    private int httpPort;
+
+    private int httpSSlPort;
+
+    private int rtmpPort;
+
+    private int rtmpSSlPort;
+
+    private int rtpProxyPort;
+
+    private int rtspPort;
+
+    private int rtspSSLPort;
+
+    private String secret;
+
+    private int recordAssistPort;
+
+
+
+    public MediaServerItemLite(MediaServerItem mediaServerItem) {
+        this.id = mediaServerItem.getId();
+        this.ip = mediaServerItem.getIp();
+        this.hookIp = mediaServerItem.getHookIp();
+        this.sdpIp = mediaServerItem.getSdpIp();
+        this.streamIp = mediaServerItem.getStreamIp();
+        this.httpPort = mediaServerItem.getHttpPort();
+        this.httpSSlPort = mediaServerItem.getHttpSSlPort();
+        this.rtmpPort = mediaServerItem.getRtmpPort();
+        this.rtmpSSlPort = mediaServerItem.getRtmpSSlPort();
+        this.rtpProxyPort = mediaServerItem.getRtpProxyPort();
+        this.rtspPort = mediaServerItem.getRtspPort();
+        this.rtspSSLPort = mediaServerItem.getRtspSSLPort();
+        this.secret = mediaServerItem.getSecret();
+        this.recordAssistPort = mediaServerItem.getRecordAssistPort();
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getIp() {
+        return ip;
+    }
+
+    public void setIp(String ip) {
+        this.ip = ip;
+    }
+
+    public String getHookIp() {
+        return hookIp;
+    }
+
+    public void setHookIp(String hookIp) {
+        this.hookIp = hookIp;
+    }
+
+    public String getSdpIp() {
+        return sdpIp;
+    }
+
+    public void setSdpIp(String sdpIp) {
+        this.sdpIp = sdpIp;
+    }
+
+    public String getStreamIp() {
+        return streamIp;
+    }
+
+    public void setStreamIp(String streamIp) {
+        this.streamIp = streamIp;
+    }
+
+    public int getHttpPort() {
+        return httpPort;
+    }
+
+    public void setHttpPort(int httpPort) {
+        this.httpPort = httpPort;
+    }
+
+    public int getHttpSSlPort() {
+        return httpSSlPort;
+    }
+
+    public void setHttpSSlPort(int httpSSlPort) {
+        this.httpSSlPort = httpSSlPort;
+    }
+
+    public int getRtmpPort() {
+        return rtmpPort;
+    }
+
+    public void setRtmpPort(int rtmpPort) {
+        this.rtmpPort = rtmpPort;
+    }
+
+    public int getRtmpSSlPort() {
+        return rtmpSSlPort;
+    }
+
+    public void setRtmpSSlPort(int rtmpSSlPort) {
+        this.rtmpSSlPort = rtmpSSlPort;
+    }
+
+    public int getRtpProxyPort() {
+        return rtpProxyPort;
+    }
+
+    public void setRtpProxyPort(int rtpProxyPort) {
+        this.rtpProxyPort = rtpProxyPort;
+    }
+
+    public int getRtspPort() {
+        return rtspPort;
+    }
+
+    public void setRtspPort(int rtspPort) {
+        this.rtspPort = rtspPort;
+    }
+
+    public int getRtspSSLPort() {
+        return rtspSSLPort;
+    }
+
+    public void setRtspSSLPort(int rtspSSLPort) {
+        this.rtspSSLPort = rtspSSLPort;
+    }
+
+
+    public String getSecret() {
+        return secret;
+    }
+
+    public void setSecret(String secret) {
+        this.secret = secret;
+    }
+
+    public int getRecordAssistPort() {
+        return recordAssistPort;
+    }
+
+    public void setRecordAssistPort(int recordAssistPort) {
+        this.recordAssistPort = recordAssistPort;
+    }
+}

+ 4 - 2
src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java

@@ -45,8 +45,10 @@ public interface IMediaServerService {
 
     void updateVmServer(List<MediaServerItem>  mediaServerItemList);
 
-    SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck,
-                           boolean isPlayback, Integer port, Boolean reUsePort, Integer tcpMode);
+    SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String presetSsrc, boolean ssrcCheck,
+                           boolean isPlayback, Integer port, Boolean onlyAuto, Boolean reUsePort, Integer tcpMode);
+
+    SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck, boolean isPlayback, Integer port, Boolean onlyAuto);
 
     void closeRTPServer(MediaServerItem mediaServerItem, String streamId);
 

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/service/IMediaService.java

@@ -40,5 +40,5 @@ public interface IMediaService {
      * @param stream
      * @return
      */
-    StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, Object tracks, String addr, String callId);
+    StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, Object tracks, String addr, String callId, boolean isPlay);
 }

+ 25 - 0
src/main/java/com/genersoft/iot/vmp/service/IPlatformService.java

@@ -1,9 +1,18 @@
 package com.genersoft.iot.vmp.service;
 
+import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
+import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
+import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import com.genersoft.iot.vmp.service.bean.InviteTimeOutCallback;
 import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
 import com.github.pagehelper.PageInfo;
 
+import javax.sip.InvalidArgumentException;
+import javax.sip.SipException;
+import java.text.ParseException;
+
 /**
  * 国标平台的业务类
  * @author lin
@@ -56,5 +65,21 @@ public interface IPlatformService {
      */
     void sendNotifyMobilePosition(String platformId);
 
+    /**
+     * 向上级发送语音喊话的消息
+     * @param platform 平台
+     * @param channelId 通道
+     * @param hookEvent hook事件
+     * @param errorEvent 信令错误事件
+     * @param timeoutCallback 超时事件
+     */
+    void broadcastInvite(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem,  ZlmHttpHookSubscribe.Event hookEvent,
+                         SipSubscribe.Event errorEvent, InviteTimeOutCallback timeoutCallback) throws InvalidArgumentException, ParseException, SipException;
+
+    /**
+     * 语音喊话回复BYE
+     */
+    void stopBroadcast(ParentPlatform platform, DeviceChannel channel, String stream,boolean sendBye, MediaServerItem mediaServerItem);
+
     void addSimulatedSubscribeInfo(ParentPlatform parentPlatform);
 }

+ 28 - 0
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java

@@ -1,16 +1,25 @@
 package com.genersoft.iot.vmp.service;
 
+import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.conf.exception.ServiceException;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
+import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
 import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.hook.HookParam;
 import com.genersoft.iot.vmp.service.bean.ErrorCallback;
 import com.genersoft.iot.vmp.service.bean.SSRCInfo;
+import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult;
+import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.AudioBroadcastEvent;
+import gov.nist.javax.sip.message.SIPResponse;
 
 import javax.sip.InvalidArgumentException;
 import javax.sip.SipException;
+import javax.sip.header.CallIdHeader;
 import java.text.ParseException;
+import java.util.Map;
 
 /**
  * 点播处理
@@ -21,6 +30,8 @@ public interface IPlayService {
               ErrorCallback<Object> callback);
     SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId, String ssrc, ErrorCallback<Object> callback);
 
+    StreamInfo onPublishHandlerForPlay(MediaServerItem mediaServerItem, HookParam hookParam, String deviceId, String channelId);
+
     MediaServerItem getNewMediaServerItem(Device device);
 
     void playBack(String deviceId, String channelId, String startTime, String endTime, ErrorCallback<Object> callback);
@@ -34,10 +45,27 @@ public interface IPlayService {
 
     void zlmServerOnline(String mediaServerId);
 
+    AudioBroadcastResult audioBroadcast(Device device, String channelId, Boolean broadcastMode);
+
+    boolean audioBroadcastCmd(Device device, String channelId, MediaServerItem mediaServerItem, String app, String stream, int timeout, boolean isFromPlatform, AudioBroadcastEvent event) throws InvalidArgumentException, ParseException, SipException;
+
+    boolean audioBroadcastInUse(Device device, String channelId);
+
+    void stopAudioBroadcast(String deviceId, String channelId);
+
     void pauseRtp(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException;
 
     void resumeRtp(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException;
 
+    void startPushStream(SendRtpItem sendRtpItem, SIPResponse sipResponse, ParentPlatform platform, CallIdHeader callIdHeader);
+
+    void startSendRtpStreamHand(SendRtpItem sendRtpItem, Object correlationInfo,
+                                JSONObject jsonObject, Map<String, Object> param, CallIdHeader callIdHeader);
+
+    void talkCmd(Device device, String channelId, MediaServerItem mediaServerItem, String stream, AudioBroadcastEvent event);
+
+    void stopTalk(Device device, String channelId, Boolean streamIsReady);
+
     void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback);
 
 }

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/service/bean/RequestSendItemMsg.java

@@ -72,7 +72,7 @@ public class RequestSendItemMsg {
 
 
     public static RequestSendItemMsg getInstance(String serverId, String mediaServerId, String app, String stream, String ip, int port,
-                                                 String ssrc, String platformId, String channelId, Boolean isTcp, Boolean rtcp, String platformName) {
+                                                          String ssrc, String platformId, String channelId, Boolean isTcp, Boolean rtcp, String platformName) {
         RequestSendItemMsg requestSendItemMsg = new RequestSendItemMsg();
         requestSendItemMsg.setServerId(serverId);
         requestSendItemMsg.setMediaServerId(mediaServerId);

+ 30 - 4
src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java

@@ -6,6 +6,7 @@ import com.genersoft.iot.vmp.common.VideoManagerConstants;
 import com.genersoft.iot.vmp.conf.DynamicTask;
 import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.*;
+import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
 import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask;
 import com.genersoft.iot.vmp.gb28181.task.impl.CatalogSubscribeTask;
@@ -13,6 +14,8 @@ import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeTask;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler;
+import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.service.IDeviceChannelService;
 import com.genersoft.iot.vmp.service.IDeviceService;
 import com.genersoft.iot.vmp.service.IInviteStreamService;
@@ -37,9 +40,7 @@ import javax.sip.InvalidArgumentException;
 import javax.sip.SipException;
 import java.text.ParseException;
 import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
+import java.util.*;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -98,6 +99,12 @@ public class DeviceServiceImpl implements IDeviceService {
     @Autowired
     private IMediaServerService mediaServerService;
 
+    @Autowired
+    private AudioBroadcastManager audioBroadcastManager;
+
+    @Autowired
+    private ZLMRESTfulUtils zlmresTfulUtils;
+
     @Override
     public void online(Device device, SipTransactionInfo sipTransactionInfo) {
         logger.info("[设备上线] deviceId:{}->{}:{}", device.getDeviceId(), device.getIp(), device.getPort());
@@ -229,6 +236,25 @@ public class DeviceServiceImpl implements IDeviceService {
         // 移除订阅
         removeCatalogSubscribe(device, null);
         removeMobilePositionSubscribe(device, null);
+
+        List<AudioBroadcastCatch> audioBroadcastCatches = audioBroadcastManager.get(deviceId);
+        if (audioBroadcastCatches.size() > 0) {
+            for (AudioBroadcastCatch audioBroadcastCatch : audioBroadcastCatches) {
+
+                SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(deviceId, audioBroadcastCatch.getChannelId(), null, null);
+                if (sendRtpItem != null) {
+                    redisCatchStorage.deleteSendRTPServer(deviceId, sendRtpItem.getChannelId(), null, null);
+                    MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
+                    Map<String, Object> param = new HashMap<>();
+                    param.put("vhost", "__defaultVhost__");
+                    param.put("app", sendRtpItem.getApp());
+                    param.put("stream", sendRtpItem.getStream());
+                    zlmresTfulUtils.stopSendRtp(mediaInfo, param);
+                }
+
+                audioBroadcastManager.del(deviceId, audioBroadcastCatch.getChannelId());
+            }
+        }
     }
 
     @Override
@@ -569,7 +595,7 @@ public class DeviceServiceImpl implements IDeviceService {
         deviceInStore.setSsrcCheck(device.isSsrcCheck());
         //作为消息通道
         deviceInStore.setAsMessageChannel(device.isAsMessageChannel());
-        
+
         deviceMapper.updateCustom(deviceInStore);
         redisCatchStorage.updateDevice(deviceInStore);
     }

+ 12 - 3
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java

@@ -119,6 +119,8 @@ public class MediaServerServiceImpl implements IMediaServerService {
 
 
 
+
+
     /**
      * 初始化
      */
@@ -145,7 +147,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
 
     @Override
     public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String presetSsrc, boolean ssrcCheck,
-                                  boolean isPlayback, Integer port, Boolean reUsePort, Integer tcpMode) {
+                                  boolean isPlayback, Integer port, Boolean onlyAuto, Boolean reUsePort, Integer tcpMode) {
         if (mediaServerItem == null || mediaServerItem.getId() == null) {
             logger.info("[openRTPServer] 失败, mediaServerItem == null || mediaServerItem.getId() == null");
             return null;
@@ -171,13 +173,19 @@ public class MediaServerServiceImpl implements IMediaServerService {
         }
         int rtpServerPort;
         if (mediaServerItem.isRtpEnable()) {
-            rtpServerPort = zlmServerFactory.createRTPServer(mediaServerItem, streamId, ssrcCheck ? Long.parseLong(ssrc) : 0, port, reUsePort, tcpMode);
+            rtpServerPort = zlmServerFactory.createRTPServer(mediaServerItem, streamId, ssrcCheck ? Long.parseLong(ssrc) : 0, port, onlyAuto, reUsePort, tcpMode);
         } else {
             rtpServerPort = mediaServerItem.getRtpProxyPort();
         }
         return new SSRCInfo(rtpServerPort, ssrc, streamId);
     }
 
+    @Override
+    public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck, boolean isPlayback, Integer port, Boolean onlyAuto) {
+        return openRTPServer(mediaServerItem, streamId, ssrc, ssrcCheck, isPlayback, port, onlyAuto, null, 0);
+    }
+
+
     @Override
     public void closeRTPServer(MediaServerItem mediaServerItem, String streamId) {
         if (mediaServerItem == null) {
@@ -198,7 +206,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
     @Override
     public void closeRTPServer(String mediaServerId, String streamId) {
         MediaServerItem mediaServerItem = this.getOne(mediaServerId);
-        if (mediaServerItem.isRtpEnable()) {
+        if (mediaServerItem != null && mediaServerItem.isRtpEnable()) {
             closeRTPServer(mediaServerItem, streamId);
         }
         zlmresTfulUtils.closeStreams(mediaServerItem, "rtp", streamId);
@@ -306,6 +314,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
         return JsonUtil.redisJsonToObject(redisTemplate, key, MediaServerItem.class);
     }
 
+
     @Override
     public MediaServerItem getDefaultMediaServer() {
         return mediaServerMapper.queryDefault();

+ 6 - 10
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java

@@ -4,21 +4,18 @@ import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONArray;
 import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.common.StreamInfo;
-import com.genersoft.iot.vmp.common.StreamURL;
 import com.genersoft.iot.vmp.conf.MediaConfig;
 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
 import com.genersoft.iot.vmp.service.IMediaServerService;
+import com.genersoft.iot.vmp.service.IMediaService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
-import com.genersoft.iot.vmp.service.IMediaService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.util.ObjectUtils;
 
-import java.net.URL;
-
 @Service
 public class MediaServiceImpl implements IMediaService {
 
@@ -42,7 +39,7 @@ public class MediaServiceImpl implements IMediaService {
 
     @Override
     public StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, Object tracks, String callId) {
-        return getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, null, callId);
+        return getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, null, callId, true);
     }
 
     @Override
@@ -70,9 +67,9 @@ public class MediaServiceImpl implements IMediaService {
                 JSONObject mediaJSON = data.getJSONObject(0);
                 JSONArray tracks = mediaJSON.getJSONArray("tracks");
                 if (authority) {
-                    streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, addr, calld);
+                    streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, addr, calld, true);
                 }else {
-                    streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, addr,null);
+                    streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, addr,null, true);
                 }
             }
         }
@@ -87,7 +84,7 @@ public class MediaServiceImpl implements IMediaService {
     }
 
     @Override
-    public StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, Object tracks, String addr, String callId) {
+    public StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, Object tracks, String addr, String callId, boolean isPlay) {
         StreamInfo streamInfoResult = new StreamInfo();
         streamInfoResult.setStream(stream);
         streamInfoResult.setApp(app);
@@ -104,10 +101,9 @@ public class MediaServiceImpl implements IMediaService {
         streamInfoResult.setFmp4(addr, mediaInfo.getHttpPort(),mediaInfo.getHttpSSlPort(), app,  stream, callIdParam);
         streamInfoResult.setHls(addr, mediaInfo.getHttpPort(),mediaInfo.getHttpSSlPort(), app,  stream, callIdParam);
         streamInfoResult.setTs(addr, mediaInfo.getHttpPort(),mediaInfo.getHttpSSlPort(), app,  stream, callIdParam);
-        streamInfoResult.setRtc(addr, mediaInfo.getHttpPort(),mediaInfo.getHttpSSlPort(), app,  stream, callIdParam);
+        streamInfoResult.setRtc(addr, mediaInfo.getHttpPort(),mediaInfo.getHttpSSlPort(), app,  stream, callIdParam, isPlay);
 
         streamInfoResult.setTracks(tracks);
         return streamInfoResult;
     }
-
 }

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/service/impl/PlatformChannelServiceImpl.java

@@ -164,7 +164,7 @@ public class PlatformChannelServiceImpl implements IPlatformChannelService {
             return 0;
         }
         if (ObjectUtils.isEmpty(catalogId)) {
-            catalogId = null;
+           catalogId = null;
         }
 
         List<DeviceChannel> deviceChannels = platformChannelMapper.queryAllChannelInCatalog(platformId, catalogId);

+ 356 - 11
src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java

@@ -1,34 +1,58 @@
 package com.genersoft.iot.vmp.service.impl;
 
+import com.alibaba.fastjson2.JSONObject;
+import com.genersoft.iot.vmp.common.InviteInfo;
+import com.genersoft.iot.vmp.common.InviteSessionStatus;
+import com.genersoft.iot.vmp.common.InviteSessionType;
 import com.baomidou.dynamic.datasource.annotation.DS;
 import com.genersoft.iot.vmp.conf.DynamicTask;
 import com.genersoft.iot.vmp.conf.UserSetting;
+import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
 import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
+import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
+import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
+import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
+import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
+import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
+import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
 import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam;
+import com.genersoft.iot.vmp.service.IInviteStreamService;
 import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.service.IPlatformService;
-import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
+import com.genersoft.iot.vmp.service.IPlayService;
+import com.genersoft.iot.vmp.service.bean.*;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.dao.*;
 import com.genersoft.iot.vmp.utils.DateUtil;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
+import gov.nist.javax.sip.message.SIPRequest;
+import gov.nist.javax.sip.message.SIPResponse;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import javax.sdp.*;
 import javax.sip.InvalidArgumentException;
+import javax.sip.ResponseEvent;
+import javax.sip.PeerUnavailableException;
 import javax.sip.SipException;
 import java.text.ParseException;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.*;
 
 /**
  * @author lin
@@ -47,15 +71,6 @@ public class PlatformServiceImpl implements IPlatformService {
     @Autowired
     private ParentPlatformMapper platformMapper;
 
-    @Autowired
-    private PlatformCatalogMapper catalogMapper;
-
-    @Autowired
-    private PlatformChannelMapper platformChannelMapper;
-
-    @Autowired
-    private PlatformGbStreamMapper platformGbStreamMapper;
-
     @Autowired
     private IRedisCatchStorage redisCatchStorage;
 
@@ -83,6 +98,21 @@ public class PlatformServiceImpl implements IPlatformService {
     @Autowired
     private UserSetting userSetting;
 
+    @Autowired
+    private ZlmHttpHookSubscribe subscribe;
+
+    @Autowired
+    private VideoStreamSessionManager streamSession;
+
+
+    @Autowired
+    private IPlayService playService;
+
+    @Autowired
+    private IInviteStreamService inviteStreamService;
+
+    @Autowired
+    private ZLMRESTfulUtils zlmresTfulUtils;
 
 
     @Override
@@ -374,7 +404,7 @@ public class PlatformServiceImpl implements IPlatformService {
                 Map<String, Object> param = new HashMap<>(3);
                 param.put("vhost", "__defaultVhost__");
                 param.put("app", sendRtpItem.getApp());
-                param.put("stream", sendRtpItem.getStreamId());
+                param.put("stream", sendRtpItem.getStream());
                 zlmServerFactory.stopSendRtpStream(mediaInfo, param);
             }
         }
@@ -431,4 +461,319 @@ public class PlatformServiceImpl implements IPlatformService {
             }
         }
     }
+
+    @Override
+    public void broadcastInvite(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem, ZlmHttpHookSubscribe.Event hookEvent,
+                                SipSubscribe.Event errorEvent, InviteTimeOutCallback timeoutCallback) throws InvalidArgumentException, ParseException, SipException {
+
+        if (mediaServerItem == null) {
+            logger.info("[国标级联] 语音喊话未找到可用的zlm. platform: {}", platform.getServerGBId());
+            return;
+        }
+        InviteInfo inviteInfoForOld = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, platform.getServerGBId(), channelId);
+
+        if (inviteInfoForOld != null && inviteInfoForOld.getStreamInfo() != null) {
+            // 如果zlm不存在这个流,则删除数据即可
+            MediaServerItem mediaServerItemForStreamInfo = mediaServerService.getOne(inviteInfoForOld.getStreamInfo().getMediaServerId());
+            if (mediaServerItemForStreamInfo != null) {
+                Boolean ready = zlmServerFactory.isStreamReady(mediaServerItemForStreamInfo, inviteInfoForOld.getStreamInfo().getApp(), inviteInfoForOld.getStreamInfo().getStream());
+                if (!ready) {
+                    // 错误存在于redis中的数据
+                    inviteStreamService.removeInviteInfo(inviteInfoForOld);
+                }else {
+                    // 流确实尚在推流,直接回调结果
+                    OnStreamChangedHookParam hookParam = new OnStreamChangedHookParam();
+                    hookParam.setApp(inviteInfoForOld.getStreamInfo().getApp());
+                    hookParam.setStream(inviteInfoForOld.getStreamInfo().getStream());
+
+                    hookEvent.response(mediaServerItemForStreamInfo, hookParam);
+                    return;
+                }
+            }
+        }
+
+        String streamId = null;
+        if (mediaServerItem.isRtpEnable()) {
+            streamId = String.format("%s_%s", platform.getServerGBId(), channelId);
+        }
+        // 默认不进行SSRC校验, TODO 后续可改为配置
+        boolean ssrcCheck = false;
+        int tcpMode;
+        if (userSetting.getBroadcastForPlatform().equalsIgnoreCase("TCP-PASSIVE")) {
+            tcpMode = 1;
+        }else if (userSetting.getBroadcastForPlatform().equalsIgnoreCase("TCP-ACTIVE")) {
+            tcpMode = 2;
+        } else {
+            tcpMode = 0;
+        }
+        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, null, ssrcCheck, false, null, true, false, tcpMode);
+        if (ssrcInfo == null || ssrcInfo.getPort() < 0) {
+            logger.info("[国标级联] 发起语音喊话 开启端口监听失败, platform: {}, channel: {}", platform.getServerGBId(), channelId);
+            SipSubscribe.EventResult<Object> eventResult = new SipSubscribe.EventResult<>();
+            eventResult.statusCode = -1;
+            eventResult.msg = "端口监听失败";
+            eventResult.type = SipSubscribe.EventResultType.failedToGetPort;
+            errorEvent.response(eventResult);
+            return;
+        }
+        logger.info("[国标级联] 语音喊话,发起Invite消息 deviceId: {}, channelId: {},收流端口: {}, 收流模式:{}, SSRC: {}, SSRC校验:{}",
+                platform.getServerGBId(), channelId, ssrcInfo.getPort(), userSetting.getBroadcastForPlatform(), ssrcInfo.getSsrc(), ssrcCheck);
+
+        // 初始化redis中的invite消息状态
+        InviteInfo inviteInfo = InviteInfo.getInviteInfo(platform.getServerGBId(), channelId, ssrcInfo.getStream(), ssrcInfo,
+                mediaServerItem.getSdpIp(), ssrcInfo.getPort(), userSetting.getBroadcastForPlatform(), InviteSessionType.BROADCAST,
+                InviteSessionStatus.ready);
+        inviteStreamService.updateInviteInfo(inviteInfo);
+        String timeOutTaskKey = UUID.randomUUID().toString();
+        dynamicTask.startDelay(timeOutTaskKey, () -> {
+            // 执行超时任务时查询是否已经成功,成功了则不执行超时任务,防止超时任务取消失败的情况
+            InviteInfo inviteInfoForBroadcast = inviteStreamService.getInviteInfo(InviteSessionType.BROADCAST, platform.getServerGBId(), channelId, null);
+            if (inviteInfoForBroadcast == null) {
+                logger.info("[国标级联] 发起语音喊话 收流超时 deviceId: {}, channelId: {},端口:{}, SSRC: {}", platform.getServerGBId(), channelId, ssrcInfo.getPort(), ssrcInfo.getSsrc());
+                // 点播超时回复BYE 同时释放ssrc以及此次点播的资源
+                try {
+                    commanderForPlatform.streamByeCmd(platform, channelId, ssrcInfo.getStream(), null, null);
+                } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) {
+                    logger.error("[点播超时], 发送BYE失败 {}", e.getMessage());
+                } finally {
+                    timeoutCallback.run(1, "收流超时");
+                    mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
+                    mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
+                    streamSession.remove(platform.getServerGBId(), channelId, ssrcInfo.getStream());
+                    mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
+                }
+            }
+        }, userSetting.getPlayTimeout());
+        commanderForPlatform.broadcastInviteCmd(platform, channelId, mediaServerItem, ssrcInfo, (mediaServerItemForInvite, hookParam)->{
+            logger.info("[国标级联] 发起语音喊话 收到上级推流 deviceId: {}, channelId: {}", platform.getServerGBId(), channelId);
+            dynamicTask.stop(timeOutTaskKey);
+            // hook响应
+            playService.onPublishHandlerForPlay(mediaServerItemForInvite, hookParam, platform.getServerGBId(), channelId);
+            // 收到流
+            if (hookEvent != null) {
+                hookEvent.response(mediaServerItem, hookParam);
+            }
+        }, event -> {
+
+            inviteOKHandler(event, ssrcInfo, tcpMode, ssrcCheck, mediaServerItem, platform, channelId, timeOutTaskKey,
+                    null, inviteInfo, InviteSessionType.BROADCAST);
+//            // 收到200OK 检测ssrc是否有变化,防止上级自定义了ssrc
+//            ResponseEvent responseEvent = (ResponseEvent) event.event;
+//            String contentString = new String(responseEvent.getResponse().getRawContent());
+//            // 获取ssrc
+//            int ssrcIndex = contentString.indexOf("y=");
+//            // 检查是否有y字段
+//            if (ssrcIndex >= 0) {
+//                //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
+//                String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
+//                // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
+//                if (ssrcInfo.getSsrc().equals(ssrcInResponse) || ssrcCheck) {
+//                    tcpActiveHandler(platform, )
+//                    return;
+//                }
+//                logger.info("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse);
+//                if (!mediaServerItem.isRtpEnable()) {
+//                    logger.info("[点播消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
+//                    // 释放ssrc
+//                    mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
+//                    // 单端口模式streamId也有变化,需要重新设置监听
+//                    if (!mediaServerItem.isRtpEnable()) {
+//                        // 添加订阅
+//                        HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
+//                        subscribe.removeSubscribe(hookSubscribe);
+//                        hookSubscribe.getContent().put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase());
+//                        subscribe.addSubscribe(hookSubscribe, (mediaServerItemInUse, hookParam) -> {
+//                            logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + hookParam);
+//                            dynamicTask.stop(timeOutTaskKey);
+//                            // hook响应
+//                            playService.onPublishHandlerForPlay(mediaServerItemInUse, hookParam, platform.getServerGBId(), channelId);
+//                            hookEvent.response(mediaServerItemInUse, hookParam);
+//                        });
+//                    }
+//                    // 关闭rtp server
+//                    mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
+//                    // 重新开启ssrc server
+//                    mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, false, false, ssrcInfo.getPort(), true, false, tcpMode);
+//                }
+//            }
+        }, eventResult -> {
+            // 收到错误回复
+            if (errorEvent != null) {
+                errorEvent.response(eventResult);
+            }
+        });
+    }
+
+    private void inviteOKHandler(SipSubscribe.EventResult eventResult, SSRCInfo ssrcInfo, int tcpMode, boolean ssrcCheck, MediaServerItem mediaServerItem,
+                                 ParentPlatform platform, String channelId, String timeOutTaskKey, ErrorCallback<Object> callback,
+                                 InviteInfo inviteInfo, InviteSessionType inviteSessionType){
+        inviteInfo.setStatus(InviteSessionStatus.ok);
+        ResponseEvent responseEvent = (ResponseEvent) eventResult.event;
+        String contentString = new String(responseEvent.getResponse().getRawContent());
+        System.out.println(1111);
+        System.out.println(contentString);
+        String ssrcInResponse = SipUtils.getSsrcFromSdp(contentString);
+        // 兼容回复的消息中缺少ssrc(y字段)的情况
+        if (ssrcInResponse == null) {
+            ssrcInResponse = ssrcInfo.getSsrc();
+        }
+        if (ssrcInfo.getSsrc().equals(ssrcInResponse)) {
+            // ssrc 一致
+            if (mediaServerItem.isRtpEnable()) {
+                // 多端口
+                if (tcpMode == 2) {
+                    tcpActiveHandler(platform, channelId, contentString, mediaServerItem, tcpMode, ssrcCheck,
+                            timeOutTaskKey, ssrcInfo, callback);
+                }
+            }else {
+                // 单端口
+                if (tcpMode == 2) {
+                    logger.warn("[Invite 200OK] 单端口收流模式不支持tcp主动模式收流");
+                }
+            }
+        }else {
+            logger.info("[Invite 200OK] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse);
+            // ssrc 不一致
+            if (mediaServerItem.isRtpEnable()) {
+                // 多端口
+                if (ssrcCheck) {
+                    // ssrc检验
+                    // 更新ssrc
+                    logger.info("[Invite 200OK] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
+                    // 释放ssrc
+                    mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
+                    Boolean result = mediaServerService.updateRtpServerSSRC(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse);
+                    if (!result) {
+                        try {
+                            logger.warn("[Invite 200OK] 更新ssrc失败,停止喊话 {}/{}", platform.getServerGBId(), channelId);
+                            commanderForPlatform.streamByeCmd(platform, channelId, ssrcInfo.getStream(), null, null);
+                        } catch (InvalidArgumentException | SipException | ParseException | SsrcTransactionNotFoundException e) {
+                            logger.error("[命令发送失败] 停止播放, 发送BYE: {}", e.getMessage());
+                        }
+
+                        dynamicTask.stop(timeOutTaskKey);
+                        // 释放ssrc
+                        mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
+
+                        streamSession.remove(platform.getServerGBId(), channelId, ssrcInfo.getStream());
+
+                        callback.run(InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),
+                                "下级自定义了ssrc,重新设置收流信息失败", null);
+                        inviteStreamService.call(inviteSessionType, platform.getServerGBId(), channelId, null,
+                                InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),
+                                "下级自定义了ssrc,重新设置收流信息失败", null);
+
+                    }else {
+                        ssrcInfo.setSsrc(ssrcInResponse);
+                        inviteInfo.setSsrcInfo(ssrcInfo);
+                        inviteInfo.setStream(ssrcInfo.getStream());
+                        if (tcpMode == 2) {
+                            if (mediaServerItem.isRtpEnable()) {
+                                tcpActiveHandler(platform, channelId, contentString, mediaServerItem, tcpMode, ssrcCheck,
+                                        timeOutTaskKey, ssrcInfo, callback);
+                            }else {
+                                logger.warn("[Invite 200OK] 单端口收流模式不支持tcp主动模式收流");
+                            }
+                        }
+                        inviteStreamService.updateInviteInfo(inviteInfo);
+                    }
+                }else {
+                    ssrcInfo.setSsrc(ssrcInResponse);
+                    inviteInfo.setSsrcInfo(ssrcInfo);
+                    inviteInfo.setStream(ssrcInfo.getStream());
+                    if (tcpMode == 2) {
+                        if (mediaServerItem.isRtpEnable()) {
+                            tcpActiveHandler(platform, channelId, contentString, mediaServerItem, tcpMode, ssrcCheck,
+                                    timeOutTaskKey, ssrcInfo, callback);
+                        }else {
+                            logger.warn("[Invite 200OK] 单端口收流模式不支持tcp主动模式收流");
+                        }
+                    }
+                    inviteStreamService.updateInviteInfo(inviteInfo);
+                }
+            }else {
+                if (ssrcInResponse != null) {
+                    // 单端口
+                    // 重新订阅流上线
+                    SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(inviteInfo.getDeviceId(),
+                            inviteInfo.getChannelId(), null, inviteInfo.getStream());
+                    streamSession.remove(inviteInfo.getDeviceId(),
+                            inviteInfo.getChannelId(), inviteInfo.getStream());
+                    inviteStreamService.updateInviteInfoForSSRC(inviteInfo, ssrcInResponse);
+                    streamSession.put(platform.getServerGBId(), channelId, ssrcTransaction.getCallId(),
+                            inviteInfo.getStream(), ssrcInResponse, mediaServerItem.getId(), (SIPResponse) responseEvent.getResponse(), inviteSessionType);
+                }
+            }
+        }
+    }
+
+
+    private void tcpActiveHandler(ParentPlatform platform, String channelId, String contentString,
+                                  MediaServerItem mediaServerItem, int tcpMode, boolean ssrcCheck,
+                                  String timeOutTaskKey, SSRCInfo ssrcInfo, ErrorCallback<Object> callback){
+        if (tcpMode != 2) {
+            return;
+        }
+
+        String substring;
+        if (contentString.indexOf("y=") > 0) {
+            substring = contentString.substring(0, contentString.indexOf("y="));
+        }else {
+            substring = contentString;
+        }
+        try {
+            SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring);
+            int port = -1;
+            Vector mediaDescriptions = sdp.getMediaDescriptions(true);
+            for (Object description : mediaDescriptions) {
+                MediaDescription mediaDescription = (MediaDescription) description;
+                Media media = mediaDescription.getMedia();
+
+                Vector mediaFormats = media.getMediaFormats(false);
+                if (mediaFormats.contains("8") || mediaFormats.contains("0")) {
+                    port = media.getMediaPort();
+                    break;
+                }
+            }
+            logger.info("[TCP主动连接对方] serverGbId: {}, channelId: {}, 连接对方的地址:{}:{}, SSRC: {}, SSRC校验:{}",
+                    platform.getServerGBId(), channelId, sdp.getConnection().getAddress(), port, ssrcInfo.getSsrc(), ssrcCheck);
+            JSONObject jsonObject = zlmresTfulUtils.connectRtpServer(mediaServerItem, sdp.getConnection().getAddress(), port, ssrcInfo.getStream());
+            logger.info("[TCP主动连接对方] 结果: {}", jsonObject);
+        } catch (SdpException e) {
+            logger.error("[TCP主动连接对方] serverGbId: {}, channelId: {}, 解析200OK的SDP信息失败", platform.getServerGBId(), channelId, e);
+            dynamicTask.stop(timeOutTaskKey);
+            mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
+            // 释放ssrc
+            mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
+
+            streamSession.remove(platform.getServerGBId(), channelId, ssrcInfo.getStream());
+
+            callback.run(InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
+                    InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
+            inviteStreamService.call(InviteSessionType.PLAY, platform.getServerGBId(), channelId, null,
+                    InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
+                    InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
+        }
+    }
+
+    @Override
+    public void stopBroadcast(ParentPlatform platform, DeviceChannel channel, String stream, boolean sendBye, MediaServerItem mediaServerItem) {
+
+        try {
+            if (sendBye) {
+                commanderForPlatform.streamByeCmd(platform, channel.getChannelId(), stream, null, null);
+            }
+        } catch (InvalidArgumentException | SipException | ParseException | SsrcTransactionNotFoundException e) {
+            logger.warn("[消息发送失败] 停止语音对讲, 平台:{},通道:{}", platform.getId(), channel.getChannelId() );
+        } finally {
+            mediaServerService.closeRTPServer(mediaServerItem, stream);
+            InviteInfo inviteInfo = inviteStreamService.getInviteInfo(null, platform.getServerGBId(), channel.getChannelId(), stream);
+            if (inviteInfo != null) {
+                // 释放ssrc
+                mediaServerService.releaseSsrc(mediaServerItem.getId(), inviteInfo.getSsrcInfo().getSsrc());
+                inviteStreamService.removeInviteInfo(inviteInfo);
+            }
+            streamSession.remove(platform.getServerGBId(), channel.getChannelId(), stream);
+        }
+    }
 }

+ 553 - 14
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java

@@ -8,19 +8,29 @@ import com.genersoft.iot.vmp.common.InviteSessionStatus;
 import com.genersoft.iot.vmp.common.InviteSessionType;
 import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.conf.DynamicTask;
+import com.genersoft.iot.vmp.conf.SipConfig;
 import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.conf.exception.ControllerException;
 import com.genersoft.iot.vmp.conf.exception.ServiceException;
 import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
+import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
+import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
+import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
+import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
 import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
 import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
 import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
+import com.genersoft.iot.vmp.media.zlm.dto.*;
+import com.genersoft.iot.vmp.media.zlm.*;
+import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
+import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
 import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForRecordMp4;
 import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
@@ -29,19 +39,32 @@ import com.genersoft.iot.vmp.media.zlm.dto.hook.HookParam;
 import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam;
 import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam;
 import com.genersoft.iot.vmp.service.*;
+import com.genersoft.iot.vmp.service.bean.*;
+import com.genersoft.iot.vmp.service.bean.ErrorCallback;
+import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
+import com.genersoft.iot.vmp.service.bean.RequestPushStreamMsg;
+import com.genersoft.iot.vmp.service.bean.SSRCInfo;
+import com.genersoft.iot.vmp.service.redisMsg.RedisGbPlayMsgListener;
 import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
 import com.genersoft.iot.vmp.service.bean.ErrorCallback;
 import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
 import com.genersoft.iot.vmp.service.bean.SSRCInfo;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
+import com.genersoft.iot.vmp.storager.dao.CloudRecordServiceMapper;
 import com.genersoft.iot.vmp.utils.CloudRecordUtils;
 import com.genersoft.iot.vmp.utils.DateUtil;
+import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
+import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
+import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.AudioBroadcastEvent;
 import gov.nist.javax.sip.message.SIPResponse;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Service;
 import org.springframework.util.ObjectUtils;
 
@@ -49,13 +72,12 @@ import javax.sdp.*;
 import javax.sip.InvalidArgumentException;
 import javax.sip.ResponseEvent;
 import javax.sip.SipException;
+import javax.sip.header.CallIdHeader;
 import java.io.File;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.text.ParseException;
-import java.util.List;
-import java.util.UUID;
-import java.util.Vector;
+import java.util.*;
 
 @SuppressWarnings(value = {"rawtypes", "unchecked"})
 @Service
@@ -71,11 +93,20 @@ public class PlayServiceImpl implements IPlayService {
     private ISIPCommander cmder;
 
     @Autowired
-    private SIPCommanderFroPlatform sipCommanderFroPlatform;
+    private AudioBroadcastManager audioBroadcastManager;
+
+    @Autowired
+    private IDeviceService deviceService;
+
+    @Autowired
+    private ISIPCommanderForPlatform sipCommanderFroPlatform;
 
     @Autowired
     private IRedisCatchStorage redisCatchStorage;
 
+    @Autowired
+    private ZLMServerFactory zlmServerFactory;
+
     @Autowired
     private IInviteStreamService inviteStreamService;
 
@@ -83,10 +114,10 @@ public class PlayServiceImpl implements IPlayService {
     private ZlmHttpHookSubscribe subscribe;
 
     @Autowired
-    private ZLMRESTfulUtils zlmresTfulUtils;
+    private SendRtpPortManager sendRtpPortManager;
 
     @Autowired
-    private ZLMServerFactory zlmServerFactory;
+    private ZLMRESTfulUtils zlmresTfulUtils;
 
     @Autowired
     private IMediaService mediaService;
@@ -98,17 +129,40 @@ public class PlayServiceImpl implements IPlayService {
     private VideoStreamSessionManager streamSession;
 
     @Autowired
-    private IDeviceService deviceService;
+    private UserSetting userSetting;
 
     @Autowired
     private IDeviceChannelService channelService;
 
     @Autowired
-    private UserSetting userSetting;
+    private SipConfig sipConfig;
 
     @Autowired
     private DynamicTask dynamicTask;
 
+    @Autowired
+    private CloudRecordServiceMapper cloudRecordServiceMapper;
+
+    @Autowired
+    private ISIPCommanderForPlatform commanderForPlatform;
+
+
+    @Qualifier("taskExecutor")
+    @Autowired
+    private ThreadPoolTaskExecutor taskExecutor;
+
+    @Autowired
+    private RedisGbPlayMsgListener redisGbPlayMsgListener;
+
+    @Autowired
+    private ZlmHttpHookSubscribe hookSubscribe;
+
+    @Autowired
+    private SSRCFactory ssrcFactory;
+
+    @Autowired
+    private RedisTemplate<Object, Object> redisTemplate;
+
 
     @Override
     public SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId, String ssrc, ErrorCallback<Object> callback) {
@@ -166,7 +220,7 @@ public class PlayServiceImpl implements IPlayService {
             }
         }
         String streamId = String.format("%s_%s", device.getDeviceId(), channelId);;
-        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, ssrc, device.isSsrcCheck(),  false, 0, false, device.getStreamModeForParam());
+        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, ssrc, device.isSsrcCheck(),  false, 0, false, false, device.getStreamModeForParam());
         if (ssrcInfo == null) {
             callback.run(InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getMsg(), null);
             inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
@@ -179,6 +233,147 @@ public class PlayServiceImpl implements IPlayService {
         return ssrcInfo;
     }
 
+    private void talk(MediaServerItem mediaServerItem, Device device, String channelId, String stream,
+                      ZlmHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent,
+                      Runnable timeoutCallback, AudioBroadcastEvent audioEvent) {
+
+        String playSsrc = ssrcFactory.getPlaySsrc(mediaServerItem.getId());
+
+        if (playSsrc == null) {
+            audioEvent.call("ssrc已经用尽");
+            return;
+        }
+        SendRtpItem sendRtpItem = new SendRtpItem();
+        sendRtpItem.setApp("talk");
+        sendRtpItem.setStream(stream);
+        sendRtpItem.setSsrc(playSsrc);
+        sendRtpItem.setDeviceId(device.getDeviceId());
+        sendRtpItem.setPlatformId(device.getDeviceId());
+        sendRtpItem.setChannelId(channelId);
+        sendRtpItem.setRtcp(false);
+        sendRtpItem.setMediaServerId(mediaServerItem.getId());
+        sendRtpItem.setOnlyAudio(true);
+        sendRtpItem.setPlayType(InviteStreamType.TALK);
+        sendRtpItem.setPt(8);
+        sendRtpItem.setStatus(1);
+        sendRtpItem.setTcpActive(false);
+        sendRtpItem.setTcp(true);
+        sendRtpItem.setUsePs(false);
+        sendRtpItem.setReceiveStream(stream + "_talk");
+
+        String callId = SipUtils.getNewCallId();
+        int port = sendRtpPortManager.getNextPort(mediaServerItem);
+        //端口获取失败的ssrcInfo 没有必要发送点播指令
+        if (port <= 0) {
+            logger.info("[语音对讲] 端口分配异常,deviceId={},channelId={}", device.getDeviceId(), channelId);
+            audioEvent.call("端口分配异常");
+            return;
+        }
+        sendRtpItem.setLocalPort(port);
+        sendRtpItem.setPort(port);
+        logger.info("[语音对讲]开始 deviceId: {}, channelId: {},收流端口: {}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channelId, sendRtpItem.getLocalPort(), device.getStreamMode(), sendRtpItem.getSsrc(), false);
+        // 超时处理
+        String timeOutTaskKey = UUID.randomUUID().toString();
+        dynamicTask.startDelay(timeOutTaskKey, () -> {
+
+            logger.info("[语音对讲] 收流超时 deviceId: {}, channelId: {},端口:{}, SSRC: {}", device.getDeviceId(), channelId, sendRtpItem.getPort(), sendRtpItem.getSsrc());
+            timeoutCallback.run();
+            // 点播超时回复BYE 同时释放ssrc以及此次点播的资源
+            try {
+                cmder.streamByeCmd(device, channelId, sendRtpItem.getStream(), null);
+            } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) {
+                logger.error("[语音对讲]超时, 发送BYE失败 {}", e.getMessage());
+            } finally {
+                timeoutCallback.run();
+                mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc());
+                streamSession.remove(device.getDeviceId(), channelId, sendRtpItem.getStream());
+            }
+        }, userSetting.getPlayTimeout());
+
+        Map<String, Object> param = new HashMap<>(12);
+        param.put("vhost","__defaultVhost__");
+        param.put("app", sendRtpItem.getApp());
+        param.put("stream", sendRtpItem.getStream());
+        param.put("ssrc", sendRtpItem.getSsrc());
+        param.put("src_port", sendRtpItem.getLocalPort());
+        param.put("pt", sendRtpItem.getPt());
+        param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0");
+        param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0");
+        param.put("is_udp", sendRtpItem.isTcp() ? "0" : "1");
+        param.put("recv_stream_id", sendRtpItem.getReceiveStream());
+        param.put("close_delay_ms", userSetting.getPlayTimeout() * 1000);
+
+        zlmServerFactory.startSendRtpPassive(mediaServerItem, param, jsonObject -> {
+            if (jsonObject == null || jsonObject.getInteger("code") != 0 ) {
+                mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc());
+                logger.info("[语音对讲]失败 deviceId: {}, channelId: {}", device.getDeviceId(), channelId);
+                audioEvent.call("失败, " + jsonObject.getString("msg"));
+                // 查看是否已经建立了通道,存在则发送bye
+                stopTalk(device, channelId);
+            }
+        });
+
+
+        // 查看设备是否已经在推流
+        try {
+            cmder.talkStreamCmd(mediaServerItem, sendRtpItem, device, channelId, callId, (mediaServerItemInuse, hookParam) -> {
+                logger.info("[语音对讲] 流已生成, 开始推流: " + hookParam);
+                dynamicTask.stop(timeOutTaskKey);
+                // TODO 暂不做处理
+            }, (mediaServerItemInuse, hookParam) -> {
+                logger.info("[语音对讲] 设备开始推流: " + hookParam);
+                dynamicTask.stop(timeOutTaskKey);
+
+            }, (event) -> {
+                dynamicTask.stop(timeOutTaskKey);
+
+                if (event.event instanceof ResponseEvent) {
+                    ResponseEvent responseEvent = (ResponseEvent) event.event;
+                    if (responseEvent.getResponse() instanceof SIPResponse) {
+                        SIPResponse response = (SIPResponse) responseEvent.getResponse();
+                        sendRtpItem.setFromTag(response.getFromTag());
+                        sendRtpItem.setToTag(response.getToTag());
+                        sendRtpItem.setCallId(response.getCallIdHeader().getCallId());
+                        redisCatchStorage.updateSendRTPSever(sendRtpItem);
+
+                        streamSession.put(device.getDeviceId(), channelId, "talk",
+                                sendRtpItem.getStream(), sendRtpItem.getSsrc(), sendRtpItem.getMediaServerId(),
+                                response, InviteSessionType.TALK);
+                    } else {
+                        logger.error("[语音对讲]收到的消息错误,response不是SIPResponse");
+                    }
+                } else {
+                    logger.error("[语音对讲]收到的消息错误,event不是ResponseEvent");
+                }
+
+            }, (event) -> {
+                dynamicTask.stop(timeOutTaskKey);
+                mediaServerService.closeRTPServer(mediaServerItem, sendRtpItem.getStream());
+                // 释放ssrc
+                mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc());
+                streamSession.remove(device.getDeviceId(), channelId, sendRtpItem.getStream());
+                errorEvent.response(event);
+            });
+        } catch (InvalidArgumentException | SipException | ParseException e) {
+
+            logger.error("[命令发送失败] 对讲消息: {}", e.getMessage());
+            dynamicTask.stop(timeOutTaskKey);
+            mediaServerService.closeRTPServer(mediaServerItem, sendRtpItem.getStream());
+            // 释放ssrc
+            mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc());
+
+            streamSession.remove(device.getDeviceId(), channelId, sendRtpItem.getStream());
+            SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult();
+            eventResult.type = SipSubscribe.EventResultType.cmdSendFailEvent;
+            eventResult.statusCode = -1;
+            eventResult.msg = "命令发送失败";
+            errorEvent.response(eventResult);
+        }
+//        }
+
+    }
+
+
 
     @Override
     public void play(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, DeviceChannel channel,
@@ -343,7 +538,22 @@ public class PlayServiceImpl implements IPlayService {
             }
             logger.info("[TCP主动连接对方] deviceId: {}, channelId: {}, 连接对方的地址:{}:{}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channelId, sdp.getConnection().getAddress(), port, device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck());
             JSONObject jsonObject = zlmresTfulUtils.connectRtpServer(mediaServerItem, sdp.getConnection().getAddress(), port, ssrcInfo.getStream());
-            logger.info("[TCP主动连接对方] 结果: {}", jsonObject);
+            logger.info("[TCP主动连接对方] 结果: {}" , jsonObject);
+            if (jsonObject.getInteger("code") != 0) {
+                // 主动连接失败,结束流程, 清理数据
+                dynamicTask.stop(timeOutTaskKey);
+                mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
+                // 释放ssrc
+                mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
+
+                streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
+
+                callback.run(InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
+                        InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
+                inviteStreamService.call(InviteSessionType.BROADCAST, device.getDeviceId(), channelId, null,
+                        InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
+                        InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
+            }
         } catch (SdpException e) {
             logger.error("[TCP主动连接对方] deviceId: {}, channelId: {}, 解析200OK的SDP信息失败", device.getDeviceId(), channelId, e);
             dynamicTask.stop(timeOutTaskKey);
@@ -355,7 +565,7 @@ public class PlayServiceImpl implements IPlayService {
 
             callback.run(InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
                     InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
-            inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
+            inviteStreamService.call(InviteSessionType.BROADCAST, device.getDeviceId(), channelId, null,
                     InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
                     InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
         }
@@ -383,7 +593,7 @@ public class PlayServiceImpl implements IPlayService {
         zlmresTfulUtils.getSnap(mediaServerItemInuse, streamUrl, 15, 1, path, fileName);
     }
 
-    private StreamInfo onPublishHandlerForPlay(MediaServerItem mediaServerItem, HookParam hookParam, String deviceId, String channelId) {
+    public StreamInfo onPublishHandlerForPlay(MediaServerItem mediaServerItem, HookParam hookParam, String deviceId, String channelId) {
         StreamInfo streamInfo = null;
         Device device = redisCatchStorage.getDevice(deviceId);
         OnStreamChangedHookParam streamChangedHookParam = (OnStreamChangedHookParam)hookParam;
@@ -466,7 +676,7 @@ public class PlayServiceImpl implements IPlayService {
                 .replace(":", "")
                 .replace(" ", "");
         String stream = deviceId + "_" + channelId + "_" + startTimeStr + "_" + endTimeTimeStr;
-        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, stream, null, device.isSsrcCheck(),  true, 0, false, device.getStreamModeForParam());
+        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, stream, null, device.isSsrcCheck(),  true, 0, false,  false, device.getStreamModeForParam());
         playBack(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, callback);
     }
 
@@ -659,7 +869,7 @@ public class PlayServiceImpl implements IPlayService {
             return;
         }
         // 录像下载不使用固定流地址,固定流地址会导致如果开始时间与结束时间一致时文件错误的叠加在一起
-        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, null, device.isSsrcCheck(),  true, 0, false, device.getStreamModeForParam());
+        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, null, device.isSsrcCheck(),  true, 0, false,false, device.getStreamModeForParam());
         download(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, downloadSpeed, callback);
     }
 
@@ -898,6 +1108,142 @@ public class PlayServiceImpl implements IPlayService {
         }
     }
 
+    @Override
+    public AudioBroadcastResult audioBroadcast(Device device, String channelId, Boolean broadcastMode) {
+        // TODO 必须多端口模式才支持语音喊话鹤语音对讲
+        if (device == null || channelId == null) {
+            return null;
+        }
+        logger.info("[语音喊话] device: {}, channel: {}", device.getDeviceId(), channelId);
+        DeviceChannel deviceChannel = storager.queryChannel(device.getDeviceId(), channelId);
+        if (deviceChannel == null) {
+            logger.warn("开启语音广播的时候未找到通道: {}", channelId);
+            return null;
+        }
+        MediaServerItem mediaServerItem = mediaServerService.getMediaServerForMinimumLoad(null);
+        if (broadcastMode == null) {
+            broadcastMode = true;
+        }
+        String app = broadcastMode?"broadcast":"talk";
+        String stream = device.getDeviceId() + "_" + channelId;
+        AudioBroadcastResult audioBroadcastResult = new AudioBroadcastResult();
+        audioBroadcastResult.setApp(app);
+        audioBroadcastResult.setStream(stream);
+        audioBroadcastResult.setStreamInfo(new StreamContent(mediaService.getStreamInfoByAppAndStream(mediaServerItem, app, stream, null, null, null, false)));
+        audioBroadcastResult.setCodec("G.711");
+        return audioBroadcastResult;
+    }
+
+    @Override
+    public boolean audioBroadcastCmd(Device device, String channelId, MediaServerItem mediaServerItem, String app, String stream, int timeout, boolean isFromPlatform, AudioBroadcastEvent event) throws InvalidArgumentException, ParseException, SipException {
+        if (device == null || channelId == null) {
+            return false;
+        }
+        logger.info("[语音喊话] device: {}, channel: {}", device.getDeviceId(), channelId);
+        DeviceChannel deviceChannel = storager.queryChannel(device.getDeviceId(), channelId);
+        if (deviceChannel == null) {
+            logger.warn("开启语音广播的时候未找到通道: {}", channelId);
+            event.call("开启语音广播的时候未找到通道");
+            return false;
+        }
+        // 查询通道使用状态
+        if (audioBroadcastManager.exit(device.getDeviceId(), channelId)) {
+            SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, null, null);
+            if (sendRtpItem != null && sendRtpItem.isOnlyAudio()) {
+                // 查询流是否存在,不存在则认为是异常状态
+                Boolean streamReady = zlmServerFactory.isStreamReady(mediaServerItem, sendRtpItem.getApp(), sendRtpItem.getStream());
+                if (streamReady) {
+                    logger.warn("语音广播已经开启: {}", channelId);
+                    event.call("语音广播已经开启");
+                    return false;
+                } else {
+                    stopAudioBroadcast(device.getDeviceId(), channelId);
+                }
+            }
+        }
+//        SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, null, null);
+//        if (sendRtpItem != null) {
+//            MediaServerItem mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId());
+//            Boolean streamReady = zlmServerFactory.isStreamReady(mediaServer, sendRtpItem.getApp(), sendRtpItem.getStream());
+//            if (streamReady) {
+//                logger.warn("[语音对讲] 进行中: {}", channelId);
+//                event.call("语音对讲进行中");
+//                return false;
+//            } else {
+//                stopTalk(device, channelId);
+//            }
+//        }
+
+        // 发送通知
+        cmder.audioBroadcastCmd(device, channelId, eventResultForOk -> {
+            // 发送成功
+            AudioBroadcastCatch audioBroadcastCatch = new AudioBroadcastCatch(device.getDeviceId(), channelId, mediaServerItem, app, stream, event, AudioBroadcastCatchStatus.Ready, isFromPlatform);
+            audioBroadcastManager.update(audioBroadcastCatch);
+        }, eventResultForError -> {
+            // 发送失败
+            logger.error("语音广播发送失败: {}:{}", channelId, eventResultForError.msg);
+            event.call("语音广播发送失败");
+            stopAudioBroadcast(device.getDeviceId(), channelId);
+        });
+        return true;
+    }
+
+    @Override
+    public boolean audioBroadcastInUse(Device device, String channelId) {
+        if (audioBroadcastManager.exit(device.getDeviceId(), channelId)) {
+            SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, null, null);
+            if (sendRtpItem != null && sendRtpItem.isOnlyAudio()) {
+                // 查询流是否存在,不存在则认为是异常状态
+                MediaServerItem mediaServerServiceOne = mediaServerService.getOne(sendRtpItem.getMediaServerId());
+                Boolean streamReady = zlmServerFactory.isStreamReady(mediaServerServiceOne, sendRtpItem.getApp(), sendRtpItem.getStream());
+                if (streamReady) {
+                    logger.warn("语音广播通道使用中: {}", channelId);
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+
+    @Override
+    public void stopAudioBroadcast(String deviceId, String channelId) {
+        logger.info("[停止对讲] 设备:{}, 通道:{}", deviceId, channelId);
+        List<AudioBroadcastCatch> audioBroadcastCatchList = new ArrayList<>();
+        if (channelId == null) {
+            audioBroadcastCatchList.addAll(audioBroadcastManager.get(deviceId));
+        } else {
+            audioBroadcastCatchList.add(audioBroadcastManager.get(deviceId, channelId));
+        }
+        if (audioBroadcastCatchList.size() > 0) {
+            for (AudioBroadcastCatch audioBroadcastCatch : audioBroadcastCatchList) {
+                Device device = deviceService.getDevice(deviceId);
+                if (device == null || audioBroadcastCatch == null) {
+                    return;
+                }
+                SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(deviceId, audioBroadcastCatch.getChannelId(), null, null);
+                if (sendRtpItem != null) {
+                    redisCatchStorage.deleteSendRTPServer(deviceId, sendRtpItem.getChannelId(), null, null);
+                    MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
+                    Map<String, Object> param = new HashMap<>();
+                    param.put("vhost", "__defaultVhost__");
+                    param.put("app", sendRtpItem.getApp());
+                    param.put("stream", sendRtpItem.getStream());
+                    zlmresTfulUtils.stopSendRtp(mediaInfo, param);
+                    try {
+                        cmder.streamByeCmdForDeviceInvite(device, sendRtpItem.getChannelId(), audioBroadcastCatch.getSipTransactionInfo(), null);
+                    } catch (InvalidArgumentException | ParseException | SipException |
+                             SsrcTransactionNotFoundException e) {
+                        logger.error("[消息发送失败] 发送语音喊话BYE失败");
+                    }
+                }
+
+                audioBroadcastManager.del(deviceId, channelId);
+            }
+        }
+    }
+
+
     @Override
     public void zlmServerOnline(String mediaServerId) {
         // TODO 查找之前的点播,流如果不存在则给下级发送bye
@@ -1006,6 +1352,199 @@ public class PlayServiceImpl implements IPlayService {
         cmder.playResumeCmd(device, inviteInfo.getStreamInfo());
     }
 
+    @Override
+    public void startPushStream(SendRtpItem sendRtpItem, SIPResponse sipResponse, ParentPlatform platform, CallIdHeader callIdHeader) {
+        // 开始发流
+        String is_Udp = sendRtpItem.isTcp() ? "0" : "1";
+        MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
+        logger.info("[开始推流] rtp/{}, 目标={}:{},SSRC={}, RTCP={}", sendRtpItem.getStream(),
+                sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isRtcp());
+        Map<String, Object> param = new HashMap<>(12);
+        param.put("vhost", "__defaultVhost__");
+        param.put("app", sendRtpItem.getApp());
+        param.put("stream", sendRtpItem.getStream());
+        param.put("ssrc", sendRtpItem.getSsrc());
+        param.put("src_port", sendRtpItem.getLocalPort());
+        param.put("pt", sendRtpItem.getPt());
+        param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0");
+        param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0");
+        param.put("is_udp", is_Udp);
+        if (!sendRtpItem.isTcp()) {
+            // udp模式下开启rtcp保活
+            param.put("udp_rtcp_timeout", sendRtpItem.isRtcp() ? "1" : "0");
+        }
+
+        if (mediaInfo == null) {
+            RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance(
+                    sendRtpItem.getMediaServerId(), sendRtpItem.getApp(), sendRtpItem.getStream(),
+                    sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isTcp(),
+                    sendRtpItem.getLocalPort(), sendRtpItem.getPt(), sendRtpItem.isUsePs(), sendRtpItem.isOnlyAudio());
+            redisGbPlayMsgListener.sendMsgForStartSendRtpStream(sendRtpItem.getServerId(), requestPushStreamMsg, json -> {
+                startSendRtpStreamHand(sendRtpItem, platform, json, param, callIdHeader);
+            });
+        } else {
+            // 如果是严格模式,需要关闭端口占用
+            JSONObject startSendRtpStreamResult = null;
+            if (sendRtpItem.getLocalPort() != 0) {
+                if (sendRtpItem.isTcpActive()) {
+                    startSendRtpStreamResult = zlmServerFactory.startSendRtpPassive(mediaInfo, param);
+                } else {
+                    param.put("dst_url", sendRtpItem.getIp());
+                    param.put("dst_port", sendRtpItem.getPort());
+                    startSendRtpStreamResult = zlmServerFactory.startSendRtpStream(mediaInfo, param);
+                }
+            } else {
+                if (sendRtpItem.isTcpActive()) {
+                    startSendRtpStreamResult = zlmServerFactory.startSendRtpPassive(mediaInfo, param);
+                } else {
+                    param.put("dst_url", sendRtpItem.getIp());
+                    param.put("dst_port", sendRtpItem.getPort());
+                    startSendRtpStreamResult = zlmServerFactory.startSendRtpStream(mediaInfo, param);
+                }
+            }
+            if (startSendRtpStreamResult != null) {
+                startSendRtpStreamHand(sendRtpItem, platform, startSendRtpStreamResult, param, callIdHeader);
+            }
+        }
+    }
+
+    @Override
+    public void startSendRtpStreamHand(SendRtpItem sendRtpItem, Object correlationInfo,
+                                       JSONObject jsonObject, Map<String, Object> param, CallIdHeader callIdHeader) {
+        if (jsonObject == null) {
+            logger.error("RTP推流失败: 请检查ZLM服务");
+        } else if (jsonObject.getInteger("code") == 0) {
+            logger.info("调用ZLM推流接口, 结果: {}", jsonObject);
+            logger.info("RTP推流成功[ {}/{} ],{}->{}, ", param.get("app"), param.get("stream"), jsonObject.getString("local_port"),
+                    sendRtpItem.isTcpActive()?"被动发流": param.get("dst_url") + ":" + param.get("dst_port"));
+        } else {
+            logger.error("RTP推流失败: {}, 参数:{}", jsonObject.getString("msg"), JSONObject.toJSONString(param));
+            if (sendRtpItem.isOnlyAudio()) {
+                Device device = deviceService.getDevice(sendRtpItem.getDeviceId());
+                AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
+                if (audioBroadcastCatch != null) {
+                    try {
+                        cmder.streamByeCmd(device, sendRtpItem.getChannelId(), audioBroadcastCatch.getSipTransactionInfo(), null);
+                    } catch (SipException | ParseException | InvalidArgumentException |
+                             SsrcTransactionNotFoundException e) {
+                        logger.error("[命令发送失败] 停止语音对讲: {}", e.getMessage());
+                    }
+                }
+            } else {
+                // 向上级平台
+                if (correlationInfo instanceof ParentPlatform) {
+                    try {
+                        ParentPlatform parentPlatform = (ParentPlatform)correlationInfo;
+                        commanderForPlatform.streamByeCmd(parentPlatform, callIdHeader.getCallId());
+                    } catch (SipException | InvalidArgumentException | ParseException e) {
+                        logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void talkCmd(Device device, String channelId, MediaServerItem mediaServerItem, String stream, AudioBroadcastEvent event) {
+        if (device == null || channelId == null) {
+            return;
+        }
+        // TODO 必须多端口模式才支持语音喊话鹤语音对讲
+        logger.info("[语音对讲] device: {}, channel: {}", device.getDeviceId(), channelId);
+        DeviceChannel deviceChannel = storager.queryChannel(device.getDeviceId(), channelId);
+        if (deviceChannel == null) {
+            logger.warn("开启语音对讲的时候未找到通道: {}", channelId);
+            event.call("开启语音对讲的时候未找到通道");
+            return;
+        }
+        // 查询通道使用状态
+        if (audioBroadcastManager.exit(device.getDeviceId(), channelId)) {
+            SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, null, null);
+            if (sendRtpItem != null && sendRtpItem.isOnlyAudio()) {
+                // 查询流是否存在,不存在则认为是异常状态
+                MediaServerItem mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId());
+                Boolean streamReady = zlmServerFactory.isStreamReady(mediaServer, sendRtpItem.getApp(), sendRtpItem.getStream());
+                if (streamReady) {
+                    logger.warn("[语音对讲] 正在语音广播,无法开启语音通话: {}", channelId);
+                    event.call("正在语音广播");
+                    return;
+                } else {
+                    stopAudioBroadcast(device.getDeviceId(), channelId);
+                }
+            }
+        }
+
+        SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, stream, null);
+        if (sendRtpItem != null) {
+            MediaServerItem mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId());
+            Boolean streamReady = zlmServerFactory.isStreamReady(mediaServer, "rtp", sendRtpItem.getReceiveStream());
+            if (streamReady) {
+                logger.warn("[语音对讲] 进行中: {}", channelId);
+                event.call("语音对讲进行中");
+                return;
+            } else {
+                stopTalk(device, channelId);
+            }
+        }
+
+        talk(mediaServerItem, device, channelId, stream, (mediaServerItem1, hookParam) -> {
+            logger.info("[语音对讲] 收到设备发来的流");
+        }, eventResult -> {
+            logger.warn("[语音对讲] 失败,{}/{}, 错误码 {} {}", device.getDeviceId(), channelId, eventResult.statusCode, eventResult.msg);
+            event.call("失败,错误码 " + eventResult.statusCode + ", " + eventResult.msg);
+        }, () -> {
+            logger.warn("[语音对讲] 失败,{}/{} 超时", device.getDeviceId(), channelId);
+            event.call("失败,超时 ");
+            stopTalk(device, channelId);
+        }, errorMsg -> {
+            logger.warn("[语音对讲] 失败,{}/{} {}", device.getDeviceId(), channelId, errorMsg);
+            event.call(errorMsg);
+            stopTalk(device, channelId);
+        });
+    }
+
+    private void stopTalk(Device device, String channelId) {
+        stopTalk(device, channelId, null);
+    }
+
+    @Override
+    public void stopTalk(Device device, String channelId, Boolean streamIsReady) {
+        logger.info("[语音对讲] 停止, {}/{}", device.getDeviceId(), channelId);
+        SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, null, null);
+        if (sendRtpItem == null) {
+            logger.info("[语音对讲] 停止失败, 未找到发送信息,可能已经停止");
+            return;
+        }
+        // 停止向设备推流
+        String mediaServerId = sendRtpItem.getMediaServerId();
+        if (mediaServerId == null) {
+            return;
+        }
+
+        MediaServerItem mediaServer = mediaServerService.getOne(mediaServerId);
+
+        if (streamIsReady == null || streamIsReady) {
+            Map<String, Object> param = new HashMap<>();
+            param.put("vhost", "__defaultVhost__");
+            param.put("app", sendRtpItem.getApp());
+            param.put("stream", sendRtpItem.getStream());
+            param.put("ssrc", sendRtpItem.getSsrc());
+            zlmServerFactory.stopSendRtpStream(mediaServer, param);
+        }
+
+        ssrcFactory.releaseSsrc(mediaServerId, sendRtpItem.getSsrc());
+
+        SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, null, sendRtpItem.getStream());
+        if (ssrcTransaction != null) {
+            try {
+                cmder.streamByeCmd(device, channelId, sendRtpItem.getStream(), null);
+            } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException  e) {
+                logger.info("[语音对讲] 停止消息发送失败,可能已经停止");
+            }
+        }
+        redisCatchStorage.deleteSendRTPServer(device.getDeviceId(), channelId,null, null);
+    }
+
     @Override
     public void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback) {
         Device device = deviceService.getDevice(deviceId);

+ 0 - 6
src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java

@@ -89,12 +89,6 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
     @Autowired
     private PlatformGbStreamMapper platformGbStreamMapper;
 
-    @Autowired
-    private EventPublisher eventPublisher;
-
-    @Autowired
-    private ParentPlatformMapper parentPlatformMapper;
-
     @Autowired
     private IGbStreamService gbStreamService;
 

+ 4 - 4
src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamCloseResponseListener.java

@@ -79,7 +79,7 @@ public class RedisPushStreamCloseResponseListener implements MessageListener {
                 for (SendRtpItem sendRtpItem : sendRtpItems) {
                     ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
                     if (parentPlatform != null) {
-                        redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(), sendRtpItem.getCallId(), sendRtpItem.getStreamId());
+                        redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(), sendRtpItem.getCallId(), sendRtpItem.getStream());
                         try {
                             commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem);
                         } catch (SipException | InvalidArgumentException | ParseException e) {
@@ -88,7 +88,7 @@ public class RedisPushStreamCloseResponseListener implements MessageListener {
                     }
                     if (push.isSelf()) {
                         // 停止向上级推流
-                        String streamId = sendRtpItem.getStreamId();
+                        String streamId = sendRtpItem.getStream();
                         Map<String, Object> param = new HashMap<>();
                         param.put("vhost","__defaultVhost__");
                         param.put("app",sendRtpItem.getApp());
@@ -96,11 +96,11 @@ public class RedisPushStreamCloseResponseListener implements MessageListener {
                         param.put("ssrc",sendRtpItem.getSsrc());
                         logger.info("[REDIS消息-推流结束] 停止向上级推流:{}", streamId);
                         MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
-                        redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(), sendRtpItem.getCallId(), sendRtpItem.getStreamId());
+                        redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(), sendRtpItem.getCallId(), sendRtpItem.getStream());
                         zlmServerFactory.stopSendRtpStream(mediaInfo, param);
                         if (InviteStreamType.PUSH == sendRtpItem.getPlayType()) {
                             MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
-                                    sendRtpItem.getApp(), sendRtpItem.getStreamId(), sendRtpItem.getChannelId(),
+                                    sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(),
                                     sendRtpItem.getPlatformId(), parentPlatform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId());
                             messageForPushChannel.setPlatFormIndex(parentPlatform.getId());
                             redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel);

+ 9 - 0
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java

@@ -43,6 +43,7 @@ public interface DeviceMapper {
             "geo_coord_sys," +
             "on_line," +
             "media_server_id," +
+            "broadcast_push_after_ack," +
             "(SELECT count(0) FROM wvp_device_channel WHERE device_id=wvp_device.device_id) as channel_count "+
             " FROM wvp_device WHERE device_id = #{deviceId}")
     Device getDeviceByDeviceId(String deviceId);
@@ -73,6 +74,7 @@ public interface DeviceMapper {
                 "subscribe_cycle_for_alarm,"+
                 "ssrc_check,"+
                 "as_message_channel,"+
+                "broadcast_push_after_ack,"+
                 "geo_coord_sys,"+
                 "on_line"+
             ") VALUES (" +
@@ -101,6 +103,7 @@ public interface DeviceMapper {
                 "#{subscribeCycleForAlarm}," +
                 "#{ssrcCheck}," +
                 "#{asMessageChannel}," +
+                "#{broadcastPushAfterAck}," +
                 "#{geoCoordSys}," +
                 "#{onLine}" +
             ")")
@@ -155,6 +158,7 @@ public interface DeviceMapper {
             "subscribe_cycle_for_alarm,"+
             "ssrc_check,"+
             "as_message_channel,"+
+            "broadcast_push_after_ack,"+
             "geo_coord_sys,"+
             "on_line,"+
             "media_server_id,"+
@@ -195,6 +199,7 @@ public interface DeviceMapper {
             "subscribe_cycle_for_alarm,"+
             "ssrc_check,"+
             "as_message_channel,"+
+            "broadcast_push_after_ack,"+
             "geo_coord_sys,"+
             "on_line"+
             " FROM wvp_device WHERE on_line = true")
@@ -225,6 +230,7 @@ public interface DeviceMapper {
             "subscribe_cycle_for_alarm,"+
             "ssrc_check,"+
             "as_message_channel,"+
+            "broadcast_push_after_ack,"+
             "geo_coord_sys,"+
             "on_line"+
             " FROM wvp_device WHERE ip = #{host} AND port=#{port}")
@@ -246,6 +252,7 @@ public interface DeviceMapper {
             "<if test=\"subscribeCycleForAlarm != null\">, subscribe_cycle_for_alarm=#{subscribeCycleForAlarm}</if>" +
             "<if test=\"ssrcCheck != null\">, ssrc_check=#{ssrcCheck}</if>" +
             "<if test=\"asMessageChannel != null\">, as_message_channel=#{asMessageChannel}</if>" +
+            "<if test=\"broadcastPushAfterAck != null\">, broadcast_push_after_ack=#{broadcastPushAfterAck}</if>" +
             "<if test=\"geoCoordSys != null\">, geo_coord_sys=#{geoCoordSys}</if>" +
             "<if test=\"mediaServerId != null\">, media_server_id=#{mediaServerId}</if>" +
             "WHERE device_id=#{deviceId}"+
@@ -262,6 +269,7 @@ public interface DeviceMapper {
             "charset,"+
             "ssrc_check,"+
             "as_message_channel,"+
+            "broadcastPushAfterAck,"+
             "geo_coord_sys,"+
             "on_line,"+
             "media_server_id"+
@@ -275,6 +283,7 @@ public interface DeviceMapper {
             "#{charset}," +
             "#{ssrcCheck}," +
             "#{asMessageChannel}," +
+            "#{broadcastPushAfterAck}," +
             "#{geoCoordSys}," +
             "#{onLine}," +
             "#{mediaServerId}" +

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java

@@ -150,7 +150,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
                 + sendRtpItem.getMediaServerId() + "_"
                 + sendRtpItem.getPlatformId() + "_"
                 + sendRtpItem.getChannelId() + "_"
-                + sendRtpItem.getStreamId() + "_"
+                + sendRtpItem.getStream() + "_"
                 + sendRtpItem.getCallId();
         redisTemplate.opsForValue().set(key, sendRtpItem);
     }

+ 0 - 6
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java

@@ -96,12 +96,6 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 	@Autowired
     private PlatformGbStreamMapper platformGbStreamMapper;
 
-	@Autowired
-    private IGbStreamService gbStreamService;
-
-	@Autowired
-    private ParentPlatformMapper parentPlatformMapper;
-
 	/**
 	 * 根据设备ID判断设备是否存在
 	 *

+ 59 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/bean/AudioBroadcastResult.java

@@ -0,0 +1,59 @@
+package com.genersoft.iot.vmp.vmanager.bean;
+
+/**
+ * @author lin
+ */
+public class AudioBroadcastResult {
+    /**
+     * 推流的各个方式流地址
+     */
+    private StreamContent streamInfo;
+
+    /**
+     * 编码格式
+     */
+    private String codec;
+
+    /**
+     * 向zlm推流的应用名
+     */
+    private String app;
+
+    /**
+     * 向zlm推流的流ID
+     */
+    private String stream;
+
+
+    public StreamContent getStreamInfo() {
+        return streamInfo;
+    }
+
+    public void setStreamInfo(StreamContent streamInfo) {
+        this.streamInfo = streamInfo;
+    }
+
+    public String getCodec() {
+        return codec;
+    }
+
+    public void setCodec(String codec) {
+        this.codec = codec;
+    }
+
+    public String getApp() {
+        return app;
+    }
+
+    public void setApp(String app) {
+        this.app = app;
+    }
+
+    public String getStream() {
+        return stream;
+    }
+
+    public void setStream(String stream) {
+        this.stream = stream;
+    }
+}

+ 37 - 58
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java

@@ -26,6 +26,7 @@ import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.utils.DateUtil;
+import com.genersoft.iot.vmp.vmanager.bean.*;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
 import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
 import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
@@ -48,6 +49,10 @@ import java.text.ParseException;
 import java.util.List;
 import java.util.UUID;
 
+
+/**
+ * @author lin
+ */
 @Tag(name  = "国标设备点播")
 
 @RestController
@@ -271,68 +276,42 @@ public class PlayController {
 
 	@Operation(summary = "语音广播命令", security = @SecurityRequirement(name = JwtUtils.HEADER))
 	@Parameter(name = "deviceId", description = "设备国标编号", required = true)
-    @GetMapping("/broadcast/{deviceId}")
-    @PostMapping("/broadcast/{deviceId}")
-    public DeferredResult<String> broadcastApi(@PathVariable String deviceId) {
-        if (logger.isDebugEnabled()) {
-            logger.debug("语音广播API调用");
-        }
-        Device device = storager.queryVideoDevice(deviceId);
-		DeferredResult<String> result = new DeferredResult<>(3 * 1000L);
-		String key  = DeferredResultHolder.CALLBACK_CMD_BROADCAST + deviceId;
-		if (resultHolder.exist(key, null)) {
-			result.setResult("设备使用中");
-			return result;
+	@Parameter(name = "deviceId", description = "通道国标编号", required = true)
+	@Parameter(name = "timeout", description = "推流超时时间(秒)", required = true)
+	@GetMapping("/broadcast/{deviceId}/{channelId}")
+	@PostMapping("/broadcast/{deviceId}/{channelId}")
+    public AudioBroadcastResult broadcastApi(@PathVariable String deviceId, @PathVariable String channelId, Integer timeout, Boolean broadcastMode) {
+		if (logger.isDebugEnabled()) {
+			logger.debug("语音广播API调用");
 		}
-		String uuid  = UUID.randomUUID().toString();
-        if (device == null) {
-
-			resultHolder.put(key, key,  result);
-			RequestMessage msg = new RequestMessage();
-			msg.setKey(key);
-			msg.setId(uuid);
-			JSONObject json = new JSONObject();
-			json.put("DeviceID", deviceId);
-			json.put("CmdType", "Broadcast");
-			json.put("Result", "Failed");
-			json.put("Description", "Device 不存在");
-			msg.setData(json);
-			resultHolder.invokeResult(msg);
-			return result;
+		Device device = storager.queryVideoDevice(deviceId);
+		if (device == null) {
+			throw new ControllerException(ErrorCode.ERROR400.getCode(), "未找到设备: " + deviceId);
 		}
-		try {
-			cmder.audioBroadcastCmd(device, (event) -> {
-				RequestMessage msg = new RequestMessage();
-				msg.setKey(key);
-				msg.setId(uuid);
-				JSONObject json = new JSONObject();
-				json.put("DeviceID", deviceId);
-				json.put("CmdType", "Broadcast");
-				json.put("Result", "Failed");
-				json.put("Description", String.format("语音广播操作失败,错误码: %s, %s", event.statusCode, event.msg));
-				msg.setData(json);
-				resultHolder.invokeResult(msg);
-			});
-		} catch (InvalidArgumentException | SipException | ParseException e) {
-			logger.error("[命令发送失败] 语音广播: {}", e.getMessage());
-			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
+		if (channelId == null) {
+			throw new ControllerException(ErrorCode.ERROR400.getCode(), "未找到通道: " + channelId);
 		}
 
-		result.onTimeout(() -> {
-			logger.warn("语音广播操作超时, 设备未返回应答指令");
-			RequestMessage msg = new RequestMessage();
-			msg.setKey(key);
-			msg.setId(uuid);
-			JSONObject json = new JSONObject();
-			json.put("DeviceID", deviceId);
-			json.put("CmdType", "Broadcast");
-			json.put("Result", "Failed");
-			json.put("Error", "Timeout. Device did not response to broadcast command.");
-			msg.setData(json);
-			resultHolder.invokeResult(msg);
-		});
-		resultHolder.put(key, uuid, result);
-		return result;
+		return playService.audioBroadcast(device, channelId, broadcastMode);
+
+	}
+
+	@Operation(summary = "停止语音广播")
+	@Parameter(name = "deviceId", description = "设备Id", required = true)
+	@Parameter(name = "channelId", description = "通道Id", required = true)
+	@GetMapping("/broadcast/stop/{deviceId}/{channelId}")
+	@PostMapping("/broadcast/stop/{deviceId}/{channelId}")
+	public void stopBroadcast(@PathVariable String deviceId, @PathVariable String channelId) {
+		if (logger.isDebugEnabled()) {
+			logger.debug("停止语音广播API调用");
+		}
+//		try {
+//			playService.stopAudioBroadcast(deviceId, channelId);
+//		} catch (InvalidArgumentException | ParseException  | SipException e) {
+//			logger.error("[命令发送失败] 停止语音: {}", e.getMessage());
+//			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " +  e.getMessage());
+//		}
+		playService.stopAudioBroadcast(deviceId, channelId);
 	}
 
 	@Operation(summary = "获取所有的ssrc", security = @SecurityRequirement(name = JwtUtils.HEADER))

+ 9 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/bean/AudioBroadcastEvent.java

@@ -0,0 +1,9 @@
+package com.genersoft.iot.vmp.vmanager.gb28181.play.bean;
+
+
+/**
+ * @author lin
+ */
+public interface AudioBroadcastEvent {
+    void call(String msg);
+}

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/vmanager/ps/PsController.java

@@ -102,7 +102,7 @@ public class PsController {
             }
         }
         String receiveKey = VideoManagerConstants.WVP_OTHER_RECEIVE_PS_INFO + userSetting.getServerId() + "_" + callId + "_"  + stream;
-        int localPort = zlmServerFactory.createRTPServer(mediaServerItem, stream, ssrcInt, null, false, tcpMode);
+        int localPort = zlmServerFactory.createRTPServer(mediaServerItem, stream, ssrcInt, null, false, false, tcpMode);
         if (localPort == 0) {
             throw new ControllerException(ErrorCode.ERROR100.getCode(), "获取端口失败");
         }

+ 2 - 2
src/main/java/com/genersoft/iot/vmp/vmanager/rtp/RtpController.java

@@ -102,8 +102,8 @@ public class RtpController {
             }
         }
         String receiveKey = VideoManagerConstants.WVP_OTHER_RECEIVE_RTP_INFO + userSetting.getServerId() + "_" + callId + "_"  + stream;
-        int localPortForVideo = zlmServerFactory.createRTPServer(mediaServerItem, stream, ssrcInt, null, false, tcpMode);
-        int localPortForAudio = zlmServerFactory.createRTPServer(mediaServerItem, stream + "_a" , ssrcInt, null, false, tcpMode);
+        int localPortForVideo = zlmServerFactory.createRTPServer(mediaServerItem, stream, ssrcInt, null, false, false, tcpMode);
+        int localPortForAudio = zlmServerFactory.createRTPServer(mediaServerItem, stream + "_a" , ssrcInt, null, false, false, tcpMode);
         if (localPortForVideo == 0 || localPortForAudio == 0) {
             throw new ControllerException(ErrorCode.ERROR100.getCode(), "获取端口失败");
         }

+ 14 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java

@@ -25,6 +25,7 @@ import org.springframework.web.bind.annotation.*;
 import javax.security.sasl.AuthenticationException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import java.time.LocalDateTime;
 import java.util.List;
 
 @Tag(name  = "用户管理")
@@ -206,4 +207,17 @@ public class UserController {
             }
         }
     }
+
+    @PostMapping("/userInfo")
+    @Operation(summary = "管理员修改普通用户密码")
+    public LoginUser getUserInfo() {
+        // 获取当前登录用户id
+        LoginUser userInfo = SecurityUtils.getUserInfo();
+
+        if (userInfo == null) {
+            throw new ControllerException(ErrorCode.ERROR100);
+        }
+        User user = userService.getUser(userInfo.getUsername(), userInfo.getPassword());
+        return new LoginUser(user, LocalDateTime.now());
+    }
 }

+ 4 - 0
src/main/resources/all-application.yml

@@ -217,12 +217,16 @@ user-settings:
     push-authority: true
     # 设备上线时是否自动同步通道
     sync-channel-on-device-online: false
+    # 国标级联语音喊话发流模式 * UDP:udp传输 TCP-ACTIVE:tcp主动模式 TCP-PASSIVE:tcp被动模式
+    broadcast-for-platform: UDP
     # 是否使用设备来源Ip作为回复IP, 不设置则为 false
     sip-use-source-ip-as-remote-address: false
     # 是否开启sip日志
     sip-log: true
     # 是否开启sql日志
     sql-log: true
+    # 收到ack消息后开始发流,默认false, 回复200ok后直接开始发流
+    push-stream-after-ack: false
     # 消息通道功能-缺少国标ID是否给所有上级发送消息
     send-to-platforms-when-id-lost: true
     # 保持通道状态,不接受notify通道状态变化, 兼容海康平台发送错误消息

BIN
src/main/resources/local.jks


+ 2 - 2
web_src/config/index.js

@@ -12,14 +12,14 @@ module.exports = {
     assetsPublicPath: '/',
     proxyTable: {
       '/debug': {
-        target: 'http://localhost:18080',
+        target: 'http://127.0.0.1:18082',
         changeOrigin: true,
         pathRewrite: {
           '^/debug': '/'
         }
       },
       '/static/snap': {
-        target: 'http://localhost:18080',
+        target: 'http://127.0.0.1:18082',
         changeOrigin: true,
         // pathRewrite: {
         //   '^/static/snap': '/static/snap'

+ 4 - 0
web_src/src/components/dialog/deviceEdit.vue

@@ -43,6 +43,9 @@
               <el-option key="UTF-8" label="UTF-8" value="utf-8"></el-option>
             </el-select>
           </el-form-item>
+          <el-form-item label="语音发送通道" prop="name">
+            <el-input v-model="form.audioChannelForReceive" clearable></el-input>
+          </el-form-item>
           <el-form-item label="地理坐标系" prop="geoCoordSys" >
             <el-select v-model="form.geoCoordSys" style="float: left; width: 100%" >
               <el-option key="WGS84" label="WGS84" value="WGS84"></el-option>
@@ -61,6 +64,7 @@
           <el-form-item label="其他选项">
             <el-checkbox label="SSRC校验" v-model="form.ssrcCheck" style="float: left"></el-checkbox>
             <el-checkbox label="作为消息通道" v-model="form.asMessageChannel" style="float: left"></el-checkbox>
+            <el-checkbox label="收到ACK后发流" v-model="form.broadcastPushAfterAck" style="float: left"></el-checkbox>
           </el-form-item>
           <el-form-item>
             <div style="float: right;">

ファイルの差分が大きいため隠しています
+ 857 - 598
web_src/src/components/dialog/devicePlayer.vue


ファイルの差分が大きいため隠しています
+ 831 - 327
web_src/static/js/ZLMRTCClient.js


ファイルの差分が大きいため隠しています
+ 1 - 1
web_src/static/js/ZLMRTCClient.js.map