648540858 2 anni fa
parent
commit
30ae9e929f
100 ha cambiato i file con 4407 aggiunte e 1347 eliminazioni
  1. 0 10
      .github/ISSUE_TEMPLATE/-------.md
  2. 21 14
      .github/ISSUE_TEMPLATE/--bug---.md
  3. 13 0
      .github/ISSUE_TEMPLATE/new.md
  4. 31 0
      .github/ISSUE_TEMPLATE/solve.md
  5. 19 22
      README.md
  6. 4 0
      doc/README.md
  7. BIN
      doc/_content/ability/_media/img_16.png
  8. 7 14
      doc/_content/qa/bug.md
  9. BIN
      doc/_media/shequ.png
  10. 5 0
      src/main/java/com/genersoft/iot/vmp/common/CommonCallback.java
  11. 126 0
      src/main/java/com/genersoft/iot/vmp/common/InviteInfo.java
  12. 11 0
      src/main/java/com/genersoft/iot/vmp/common/InviteSessionStatus.java
  13. 9 0
      src/main/java/com/genersoft/iot/vmp/common/InviteSessionType.java
  14. 5 5
      src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java
  15. 5 6
      src/main/java/com/genersoft/iot/vmp/conf/ProxyServletConfig.java
  16. 30 0
      src/main/java/com/genersoft/iot/vmp/conf/ScheduleConfig.java
  17. 7 4
      src/main/java/com/genersoft/iot/vmp/conf/SipPlatformRunner.java
  18. 29 0
      src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java
  19. 0 45
      src/main/java/com/genersoft/iot/vmp/conf/redis/RedisConfig.java
  20. 28 0
      src/main/java/com/genersoft/iot/vmp/conf/redis/RedisTemplateConfig.java
  21. 9 2
      src/main/java/com/genersoft/iot/vmp/conf/security/JwtAuthenticationFilter.java
  22. 4 1
      src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java
  23. 0 65
      src/main/java/com/genersoft/iot/vmp/conf/security/LoginFailureHandler.java
  24. 0 36
      src/main/java/com/genersoft/iot/vmp/conf/security/LoginSuccessHandler.java
  25. 3 7
      src/main/java/com/genersoft/iot/vmp/conf/security/SecurityUtils.java
  26. 0 10
      src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java
  27. 10 0
      src/main/java/com/genersoft/iot/vmp/conf/security/dto/JwtUser.java
  28. 2 9
      src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java
  29. 0 27
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/CmdSendFailEvent.java
  30. 11 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java
  31. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/InviteStreamType.java
  32. 4 4
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/SsrcTransaction.java
  33. 3 5
      src/main/java/com/genersoft/iot/vmp/gb28181/conf/ServerLoggerImpl.java
  34. 6 4
      src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java
  35. 10 7
      src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java
  36. 132 0
      src/main/java/com/genersoft/iot/vmp/gb28181/session/SSRCFactory.java
  37. 0 150
      src/main/java/com/genersoft/iot/vmp/gb28181/session/SsrcConfig.java
  38. 2 9
      src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java
  39. 5 0
      src/main/java/com/genersoft/iot/vmp/gb28181/task/SipRunner.java
  40. 2 6
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPSender.java
  41. 4 2
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
  42. 99 98
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java
  43. 123 118
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java
  44. 17 24
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
  45. 4 2
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
  46. 86 79
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java
  47. 195 189
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
  48. 281 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestForCatalogProcessor.java
  49. 45 31
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestProcessor.java
  50. 4 17
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java
  51. 30 20
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/info/InfoRequestProcessor.java
  52. 1 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java
  53. 28 11
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java
  54. 5 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java
  55. 7 6
      src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java
  56. 15 0
      src/main/java/com/genersoft/iot/vmp/jt1078/annotation/MsgId.java
  57. 115 0
      src/main/java/com/genersoft/iot/vmp/jt1078/cmd/JT1078Template.java
  58. 146 0
      src/main/java/com/genersoft/iot/vmp/jt1078/codec/decode/Jt808Decoder.java
  59. 33 0
      src/main/java/com/genersoft/iot/vmp/jt1078/codec/encode/Jt808Encoder.java
  60. 151 0
      src/main/java/com/genersoft/iot/vmp/jt1078/codec/encode/Jt808EncoderCmd.java
  61. 72 0
      src/main/java/com/genersoft/iot/vmp/jt1078/codec/netty/Jt808Handler.java
  62. 112 0
      src/main/java/com/genersoft/iot/vmp/jt1078/codec/netty/TcpServer.java
  63. 30 0
      src/main/java/com/genersoft/iot/vmp/jt1078/config/JT1078AutoConfiguration.java
  64. 51 0
      src/main/java/com/genersoft/iot/vmp/jt1078/config/JT1078Controller.java
  65. 76 0
      src/main/java/com/genersoft/iot/vmp/jt1078/proc/Header.java
  66. 116 0
      src/main/java/com/genersoft/iot/vmp/jt1078/proc/entity/Cmd.java
  67. 44 0
      src/main/java/com/genersoft/iot/vmp/jt1078/proc/factory/CodecFactory.java
  68. 50 0
      src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0001.java
  69. 32 0
      src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0002.java
  70. 27 0
      src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0004.java
  71. 56 0
      src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0100.java
  72. 36 0
      src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0102.java
  73. 32 0
      src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0200.java
  74. 190 0
      src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J1205.java
  75. 40 0
      src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/Re.java
  76. 43 0
      src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8001.java
  77. 41 0
      src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8100.java
  78. 112 0
      src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9101.java
  79. 99 0
      src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9102.java
  80. 173 0
      src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9201.java
  81. 80 0
      src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9202.java
  82. 94 0
      src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9205.java
  83. 27 0
      src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/Rs.java
  84. 114 0
      src/main/java/com/genersoft/iot/vmp/jt1078/session/Session.java
  85. 127 0
      src/main/java/com/genersoft/iot/vmp/jt1078/session/SessionManager.java
  86. 41 0
      src/main/java/com/genersoft/iot/vmp/jt1078/util/Bin.java
  87. 112 0
      src/main/java/com/genersoft/iot/vmp/jt1078/util/ClassUtil.java
  88. 208 226
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
  89. 2 1
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java
  90. 21 0
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java
  91. 73 8
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
  92. 6 3
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZlmHttpHookSubscribe.java
  93. 0 29
      src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaServerItem.java
  94. 4 0
      src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResult.java
  95. 31 0
      src/main/java/com/genersoft/iot/vmp/service/IDeviceChannelService.java
  96. 68 0
      src/main/java/com/genersoft/iot/vmp/service/IInviteStreamService.java
  97. 6 3
      src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java
  98. 12 16
      src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
  99. 6 0
      src/main/java/com/genersoft/iot/vmp/service/bean/InviteErrorCallback.java
  100. 0 0
      src/main/java/com/genersoft/iot/vmp/service/bean/InviteErrorCode.java

+ 0 - 10
.github/ISSUE_TEMPLATE/-------.md

@@ -1,10 +0,0 @@
----
-name: "[ 新功能 ]"
-about: 新功能
-title: ''
-labels: ''
-assignees: ''
-
----
-
-

+ 21 - 14
.github/ISSUE_TEMPLATE/--bug---.md

@@ -1,29 +1,36 @@
 ---
 name: "[ BUG ] "
-about: Create a report to help us improve
-title: ''
-labels: ''
+about: 关于wvp的bug,与zlm有关的建议直接在zlm的issue中提问
+title: 'BUG'
+labels: 'wvp的bug'
 assignees: ''
 
 ---
 
+**环境信息:**
+
+ - 1. 部署方式 wvp-pro docker / zlm(docker) + 编译wvp-pro/ wvp-prp + zlm都是编译部署/
+ - 2. 部署环境 windows / ubuntu/ centos ...
+ - 3. 端口开放情况
+ - 4. 是否是公网部署 
+ - 5. 是否使用https
+ - 6. 方便的话提供下使用的设备品牌或平台
+ - 7. 你做过哪些尝试
+ - 8. 代码更新时间
+
 **描述错误**
 描述下您遇到的问题
 
 **如何复现**
 有明确复现步骤的问题会很容易被解决
 
-**预期行为**
-清晰简洁的描述您期望发生的事情
+**截图**  
 
-**截图**
+**抓包文件**
+  
+**日志**
+```
+日志内容放这里, 文件的话请直接上传
+```
 
 
-**环境信息:**
- - 1. 部署方式 wvp-pro docker / zlm(docker) + 编译wvp-pro/ wvp-prp + zlm都是编译部署/
- - 2. 部署环境 windows / ubuntu/ centos ...
- - 3. 端口开放情况
- - 4. 是否是公网部署
- - 5. 是否使用https
- - 6. 方便的话提供下使用的设备品牌或平台
- - 7. 你做过哪些尝试

+ 13 - 0
.github/ISSUE_TEMPLATE/new.md

@@ -0,0 +1,13 @@
+---
+name: "[ 新功能 ]"
+about: 新功能
+title: '希望wVP实现的新功能,此功能应与你的具体业务无关'
+labels: ''
+assignees: ''
+
+---
+
+**项目的详细需求**
+
+**这样的实现什么作用**
+

+ 31 - 0
.github/ISSUE_TEMPLATE/solve.md

@@ -0,0 +1,31 @@
+---
+name: "[ 技术咨询 ] "
+about: 对于使用中遇到问题
+title: '技术咨询'
+labels: '技术咨询'
+assignees: ''
+
+---
+
+**环境信息:**
+
+ - 1. 部署方式 wvp-pro docker / zlm(docker) + 编译wvp-pro/ wvp-prp + zlm都是编译部署/
+ - 2. 部署环境 windows / ubuntu/ centos ...
+ - 3. 端口开放情况
+ - 4. 是否是公网部署 
+ - 5. 是否使用https
+ - 6. 方便的话提供下使用的设备品牌或平台
+ - 7. 你做过哪些尝试
+ - 8. 代码更新时间
+
+
+**内容描述:**
+
+**截图**  
+
+**抓包文件**
+  
+**日志**
+```
+日志内容放这里, 文件的话请直接上传
+```

+ 19 - 22
README.md

@@ -15,17 +15,21 @@ WEB VIDEO PLATFORM是一个基于GB28181-2016标准实现的开箱即用的网
 前端页面基于@Kyle MediaServerUI [https://gitee.com/kkkkk5G/MediaServerUI](https://gitee.com/kkkkk5G/MediaServerUI) 进行修改.  
 
 # 应用场景:
-支持浏览器无插件播放摄像头视频。  
-支持摄像机、平台、NVR等设备接入。 
+支持浏览器无插件播放摄像头视频。
+支持国标设备(摄像机、平台、NVR等)设备接入
+支持非国标(onvif, rtsp, rtmp,直播设备等等)设备接入,充分利旧。 
 支持国标级联。多平台级联。跨网视频预览。
-支持rtsp/rtmp等视频流转发到国标平台。  
-支持rtsp/rtmp等推流转发到国标平台。  
+支持跨网网闸平台互联。
 
-# 项目目标
-旨在打造一个易配置,易使用,便于维护的28181国标信令系统, 依托优秀的开源流媒体服务框架ZLMediaKit, 实现一个完整易用GB28181平台. 
 
-# 部署文档
-[doc.wvp-pro.cn](https://doc.wvp-pro.cn)
+# 文档
+wvp使用文档 [https://doc.wvp-pro.cn](https://doc.wvp-pro.cn)  
+ZLM使用文档 [https://github.com/ZLMediaKit/ZLMediaKit](https://github.com/ZLMediaKit/ZLMediaKit)
+> wvp文档由gitee提供服务,如果遇到打不开请多刷新几次。
+
+# 社群地址
+[![社群](doc/_media/shequ.png "shequ")](https://t.zsxq.com/0d8VAD3Dm)
+> 收费是为了提供更好的服务,也是对作者更大的激励。加入星球的用户三天后可以私信我留下微信号,我会拉大家入群。加入三天内不满意可以直接退款,大家不需要有顾虑,来白嫖三天也不是不可以。
 
 # gitee同步仓库
 https://gitee.com/pan648540858/wvp-GB28181-pro.git
@@ -100,23 +104,17 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git
 - [X] 云端录像,推流/代理/国标视频均可以录制在云端服务器,支持预览和下载
 - [X] 支持打包可执行jar和war
 - [X] 支持跨域请求,支持前后端分离部署
- 
-
-# 遇到问题如何解决
-国标最麻烦的地方在于设备的兼容性,所以需要大量的设备来测试,目前作者手里的设备有限,再加上作者水平有限,所以遇到问题在所难免;
-1. 查看文档网站,仔细的阅读可以帮你避免几乎所有的问题
-2. 搜索issues,这里有大部分的答案
-3. 加QQ群(901799015),这里有大量热心的小伙伴,但是前提新希望你已经仔细阅读了wiki和搜索了issues。
-4. 你可以请作者为你解答,但是我不是免费的。
-5. 你可以把遇到问题的设备寄给我,可以更容易的兼容设备和解决问题。
-
-# 使用帮助
-QQ群: 901799015, ZLM使用文档[https://github.com/ZLMediaKit/ZLMediaKit](https://github.com/ZLMediaKit/ZLMediaKit)  
-QQ私信一般不回, 精力有限.欢迎大家在群里讨论.觉得项目对你有帮助,欢迎star和提交pr。
 
 # 授权协议
 本项目自有代码使用宽松的MIT协议,在保留版权信息的情况下可以自由应用于各自商用、非商业的项目。 但是本项目也零碎的使用了一些其他的开源代码,在商用的情况下请自行替代或剔除; 由于使用本项目而产生的商业纠纷或侵权行为一概与本项目及开发者无关,请自行承担法律风险。 在使用本项目代码时,也应该在授权协议中同时表明本项目依赖的第三方库的协议
 
+# 技术支持  
+
+[知识星球](https://t.zsxq.com/0d8VAD3Dm)专栏列表:
+- [使用入门系列一:WVP-PRO能做什么](https://t.zsxq.com/0dLguVoSp)
+
+有偿技术支持,请发送邮件到648540858@qq.com
+
 # 致谢
 感谢作者[夏楚](https://github.com/xia-chu) 提供这么棒的开源流媒体服务框架,并在开发过程中给予支持与帮助。     
 感谢作者[dexter langhuihui](https://github.com/langhuihui) 开源这么好用的WEB播放器。     
@@ -128,7 +126,6 @@ QQ私信一般不回, 精力有限.欢迎大家在群里讨论.觉得项目对
 [ydpd](https://github.com/ydpd) [szy833](https://github.com/szy833) [ydwxb](https://github.com/ydwxb) [Albertzhu666](https://github.com/Albertzhu666)
 [mk1990](https://github.com/mk1990) [SaltFish001](https://github.com/SaltFish001)
 
-ps: 刚增加了这个名单,肯定遗漏了一些大佬,欢迎大佬联系我添加。
 
 ffmpeg -re -i 123.mp3 -acodec pcm_alaw -ar 8000 -ac 1  -f rtsp rtsp://192.168.1.3:30554/broadcast/34020000001320000101_34020000001310000001
 

+ 4 - 0
doc/README.md

@@ -14,6 +14,10 @@
 - 完全开源,且使用MIT许可协议。保留版权的情况下可以用于商业项目。
 - 支持多流媒体节点负载均衡。
 
+# 社群
+[![社群](_media/shequ.png "shequ")](https://t.zsxq.com/0d8VAD3Dm)
+> 收费是为了提供更好的服务,也是对作者更大的激励。加入星球的用户三天后可以私信我留下微信号,我会拉大家入群。加入三天内不满意可以直接退款,大家不需要有顾虑,来白嫖三天也不是不可以。
+
 # 我们实现了哪些国标功能
 **作为上级平台**
 - [X] 注册

BIN
doc/_content/ability/_media/img_16.png


+ 7 - 14
doc/_content/qa/bug.md

@@ -2,18 +2,11 @@
 # 反馈bug
 代码是在不断的完善的,不断修改会修复旧的问题也有可能引入新的问题,所以遇到BUG是很正常的一件事。所以遇到问题不要烦燥,咱们就事论事就好了。
 ## 如何反馈
-1. 更新代码,很可能你遇到问题别人已经更早的遇到了,或者是作者自己发现了,已经解决了,所以你可以更新代码再次进行测试;
-2. 可以在github提ISSUE,我几乎每天都会去看issue,你的问题我会尽快给予答复;
-3. 你可以来我的QQ群里,询问群友看看是否遇到了同样的问题;
-4. 你可以私聊我的QQ,如果我有时间我会给你答复,但是除非你有明确的复现步骤或者修复方案,否则你可能等不到我的答复。
-
-## 如何快速解决BUG
-目前解决BUG有三种方式:
-1. 作者验证以及修复;
-2. 热心开发者提来的PR;
-3. 使用运维手段屏蔽BUG的影响。
-
-- 对于第一种:详细的复现步骤,完整的抓包文件,有条理的错误分析都可以帮助作者复现问题,进而解决问题。解决问题往往不是最难的,复现才是。
-- 对于第二种:如果你是开发者,你已经发现了造成BUG的原因以及知道如何正确的修复,那么我很希望你PR,SRS的大佬经常说的,开源不是一个人的事。所以你的参与就是最大的鼓励。
-- 对于第三种:如果你有一个有经验的运维伙伴,那么部分问题是可以通过运维的手段暂时屏蔽的,在等待修复的这段时间了以保证项目的运行。
+1. 在知识星球提问。
+2. 更新代码,很可能你遇到问题别人已经更早的遇到了,或者是作者自己发现了,已经解决了,所以你可以更新代码再次进行测试;
+3. 可以在github提ISSUE,我几乎每天都会去看issue,你的问题我会尽快给予答复;
+> 有偿支持可以给我发邮件, 648540858@qq.com
 
+## 社群
+[![社群](../../_media/shequ.png "shequ")](https://t.zsxq.com/0d8VAD3Dm)
+> 收费是为了提供更好的服务,也是对作者更大的激励。加入星球的用户三天后可以私信我留下微信号,我会拉大家入群。加入三天内不满意可以直接退款,大家不需要有顾虑,来白嫖三天也不是不可以。

BIN
doc/_media/shequ.png


+ 5 - 0
src/main/java/com/genersoft/iot/vmp/common/CommonCallback.java

@@ -0,0 +1,5 @@
+package com.genersoft.iot.vmp.common;
+
+public interface CommonCallback<T>{
+    public void run(T t);
+}

+ 126 - 0
src/main/java/com/genersoft/iot/vmp/common/InviteInfo.java

@@ -0,0 +1,126 @@
+package com.genersoft.iot.vmp.common;
+
+import com.genersoft.iot.vmp.service.bean.SSRCInfo;
+
+/**
+ * 记录每次发送invite消息的状态
+ */
+public class InviteInfo {
+
+    private String deviceId;
+
+    private String channelId;
+
+    private String stream;
+
+    private SSRCInfo ssrcInfo;
+
+    private String receiveIp;
+
+    private Integer receivePort;
+
+    private String streamMode;
+
+    private InviteSessionType type;
+
+    private InviteSessionStatus status;
+
+    private StreamInfo streamInfo;
+
+
+    public static InviteInfo getinviteInfo(String deviceId, String channelId, String stream, SSRCInfo ssrcInfo,
+                                           String receiveIp, Integer receivePort, String streamMode,
+                                           InviteSessionType type, InviteSessionStatus status) {
+        InviteInfo inviteInfo = new InviteInfo();
+        inviteInfo.setDeviceId(deviceId);
+        inviteInfo.setChannelId(channelId);
+        inviteInfo.setStream(stream);
+        inviteInfo.setSsrcInfo(ssrcInfo);
+        inviteInfo.setReceiveIp(receiveIp);
+        inviteInfo.setReceivePort(receivePort);
+        inviteInfo.setStreamMode(streamMode);
+        inviteInfo.setType(type);
+        inviteInfo.setStatus(status);
+        return inviteInfo;
+    }
+
+    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 InviteSessionType getType() {
+        return type;
+    }
+
+    public void setType(InviteSessionType type) {
+        this.type = type;
+    }
+
+    public InviteSessionStatus getStatus() {
+        return status;
+    }
+
+    public void setStatus(InviteSessionStatus status) {
+        this.status = status;
+    }
+
+    public StreamInfo getStreamInfo() {
+        return streamInfo;
+    }
+
+    public void setStreamInfo(StreamInfo streamInfo) {
+        this.streamInfo = streamInfo;
+    }
+
+    public String getStream() {
+        return stream;
+    }
+
+    public void setStream(String stream) {
+        this.stream = stream;
+    }
+
+    public SSRCInfo getSsrcInfo() {
+        return ssrcInfo;
+    }
+
+    public void setSsrcInfo(SSRCInfo ssrcInfo) {
+        this.ssrcInfo = ssrcInfo;
+    }
+
+    public String getReceiveIp() {
+        return receiveIp;
+    }
+
+    public void setReceiveIp(String receiveIp) {
+        this.receiveIp = receiveIp;
+    }
+
+    public Integer getReceivePort() {
+        return receivePort;
+    }
+
+    public void setReceivePort(Integer receivePort) {
+        this.receivePort = receivePort;
+    }
+
+    public String getStreamMode() {
+        return streamMode;
+    }
+
+    public void setStreamMode(String streamMode) {
+        this.streamMode = streamMode;
+    }
+}

+ 11 - 0
src/main/java/com/genersoft/iot/vmp/common/InviteSessionStatus.java

@@ -0,0 +1,11 @@
+package com.genersoft.iot.vmp.common;
+
+/**
+ * 标识invite消息发出后的各个状态,
+ * 收到ok钱停止invite发送cancel,
+ * 收到200ok后发送BYE停止invite
+ */
+public enum InviteSessionStatus {
+    ready,
+    ok,
+}

+ 9 - 0
src/main/java/com/genersoft/iot/vmp/common/InviteSessionType.java

@@ -0,0 +1,9 @@
+package com.genersoft.iot.vmp.common;
+
+public enum InviteSessionType {
+    PLAY,
+    PLAYBACK,
+    DOWNLOAD,
+    BROADCAST,
+    TALK
+}

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

@@ -16,8 +16,6 @@ public class VideoManagerConstants {
 
 	public static final String MEDIA_SERVERS_ONLINE_PREFIX = "VMP_MEDIA_ONLINE_SERVERS_";
 
-	public static final String MEDIA_STREAM_PREFIX = "VMP_MEDIA_STREAM";
-
 	public static final String DEVICE_PREFIX = "VMP_DEVICE_";
 
 	// 设备同步完成
@@ -28,9 +26,10 @@ public class VideoManagerConstants {
 	public static final String KEEPLIVEKEY_PREFIX = "VMP_KEEPALIVE_";
 
 	// TODO 此处多了一个_,暂不修改
-	public static final String PLAYER_PREFIX = "VMP_PLAYER_";
-	public static final String PLAY_BLACK_PREFIX = "VMP_PLAYBACK_";
-	public static final String DOWNLOAD_PREFIX = "VMP_DOWNLOAD_";
+	public static final String INVITE_PREFIX = "VMP_INVITE";
+	public static final String PLAYER_PREFIX = "VMP_INVITE_PLAY_";
+	public static final String PLAY_BLACK_PREFIX = "VMP_INVITE_PLAYBACK_";
+	public static final String DOWNLOAD_PREFIX = "VMP_INVITE_DOWNLOAD_";
 
 	public static final String PLATFORM_KEEPALIVE_PREFIX = "VMP_PLATFORM_KEEPALIVE_";
 
@@ -123,6 +122,7 @@ public class VideoManagerConstants {
 	 */
 	public static final String VM_MSG_SUBSCRIBE_ALARM = "alarm";
 
+
 	/**
 	 * 报警通知的发送 (收到redis发出的通知,转发给其他平台)
 	 */

+ 5 - 6
src/main/java/com/genersoft/iot/vmp/conf/ProxyServletConfig.java

@@ -2,7 +2,6 @@ package com.genersoft.iot.vmp.conf;
 
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.service.IMediaServerService;
-import org.apache.catalina.connector.ClientAbortException;
 import org.apache.http.HttpHost;
 import org.apache.http.HttpRequest;
 import org.apache.http.HttpResponse;
@@ -194,11 +193,11 @@ public class ProxyServletConfig {
             } catch (IOException ioException) {
                 if (ioException instanceof ConnectException) {
                     logger.error("录像服务 连接失败");
-                }else if (ioException instanceof ClientAbortException) {
-                    /**
-                     * TODO 使用这个代理库实现代理在遇到代理视频文件时,如果是206结果,会遇到报错蛋市目前功能正常,
-                     * TODO 暂时去除异常处理。后续使用其他代理框架修改测试
-                     */
+//                }else if (ioException instanceof ClientAbortException) {
+//                    /**
+//                     * TODO 使用这个代理库实现代理在遇到代理视频文件时,如果是206结果,会遇到报错蛋市目前功能正常,
+//                     * TODO 暂时去除异常处理。后续使用其他代理框架修改测试
+//                     */
 
                 }else {
                     logger.error("录像服务 代理失败: ", e);

+ 30 - 0
src/main/java/com/genersoft/iot/vmp/conf/ScheduleConfig.java

@@ -0,0 +1,30 @@
+package com.genersoft.iot.vmp.conf;
+
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.SchedulingConfigurer;
+import org.springframework.scheduling.config.ScheduledTaskRegistrar;
+
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * "@Scheduled"是Spring框架提供的一种定时任务执行机制,默认情况下它是单线程的,在同时执行多个定时任务时可能会出现阻塞和性能问题。
+ * 为了解决这种单线程瓶颈问题,可以将定时任务的执行机制改为支持多线程
+ */
+@Configuration
+public class ScheduleConfig implements SchedulingConfigurer {
+
+	public static final int cpuNum = Runtime.getRuntime().availableProcessors();
+
+	private static final int corePoolSize = cpuNum;
+
+	private static final String threadNamePrefix = "scheduled-task-pool-%d";
+
+	@Override
+	public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
+		taskRegistrar.setScheduler(new ScheduledThreadPoolExecutor(corePoolSize,
+				new BasicThreadFactory.Builder().namingPattern(threadNamePrefix).daemon(true).build(),
+				new ThreadPoolExecutor.CallerRunsPolicy()));
+	}
+}

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

@@ -48,10 +48,13 @@ public class SipPlatformRunner implements CommandLineRunner {
             parentPlatformCatch.setParentPlatform(parentPlatform);
             parentPlatformCatch.setId(parentPlatform.getServerGBId());
             redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch);
-            // 取消订阅
-            sipCommanderForPlatform.unregister(parentPlatform, parentPlatformCatchOld.getSipTransactionInfo(), null, (eventResult)->{
-                platformService.login(parentPlatform);
-            });
+            if (parentPlatformCatchOld != null) {
+                // 取消订阅
+                sipCommanderForPlatform.unregister(parentPlatform, parentPlatformCatchOld.getSipTransactionInfo(), null, (eventResult)->{
+                    platformService.login(parentPlatform);
+                });
+            }
+
             // 设置所有平台离线
             platformService.offline(parentPlatform, true);
         }

+ 29 - 0
src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java

@@ -54,6 +54,9 @@ public class UserSetting {
 
     private Boolean refuseChannelStatusChannelFormNotify = Boolean.FALSE;
 
+    private Boolean deviceStatusNotify = Boolean.FALSE;
+    private Boolean useCustomSsrcForParentInvite = Boolean.TRUE;
+
     private String serverId = "000000";
 
     private String recordPath = null;
@@ -66,6 +69,8 @@ public class UserSetting {
 
     private List<String> allowedOrigins = new ArrayList<>();
 
+    private int maxNotifyCountQueue = 10000;
+
     public Boolean getSavePositionHistory() {
         return savePositionHistory;
     }
@@ -277,4 +282,28 @@ public class UserSetting {
     public void setRecordPath(String recordPath) {
         this.recordPath = recordPath;
     }
+
+    public int getMaxNotifyCountQueue() {
+        return maxNotifyCountQueue;
+    }
+
+    public void setMaxNotifyCountQueue(int maxNotifyCountQueue) {
+        this.maxNotifyCountQueue = maxNotifyCountQueue;
+    }
+
+    public Boolean getDeviceStatusNotify() {
+        return deviceStatusNotify;
+    }
+
+    public void setDeviceStatusNotify(Boolean deviceStatusNotify) {
+        this.deviceStatusNotify = deviceStatusNotify;
+    }
+
+    public Boolean getUseCustomSsrcForParentInvite() {
+        return useCustomSsrcForParentInvite;
+    }
+
+    public void setUseCustomSsrcForParentInvite(Boolean useCustomSsrcForParentInvite) {
+        this.useCustomSsrcForParentInvite = useCustomSsrcForParentInvite;
+    }
 }

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

@@ -1,45 +0,0 @@
-package com.genersoft.iot.vmp.conf.redis;
-
-
-import com.genersoft.iot.vmp.common.VideoManagerConstants;
-import com.genersoft.iot.vmp.service.redisMsg.*;
-import com.genersoft.iot.vmp.utils.redis.FastJsonRedisSerializer;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.cache.annotation.CachingConfigurerSupport;
-import com.alibaba.fastjson2.support.spring.data.redis.GenericFastJsonRedisSerializer;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.annotation.Order;
-import org.springframework.data.redis.connection.RedisConnectionFactory;
-import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.data.redis.serializer.StringRedisSerializer;
-
-
-/**
- * Redis中间件配置类,使用spring-data-redis集成,自动从application.yml中加载redis配置
- * swwheihei
- * 2019年5月30日 上午10:58:25
- * 
- */
-@Configuration
-@Order(value=1)
-public class RedisConfig {
-
-
-	@Bean
-	public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
-
-		RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
-		// 使用fastJson序列化
-		GenericFastJsonRedisSerializer fastJsonRedisSerializer = new GenericFastJsonRedisSerializer();
-		// value值的序列化采用fastJsonRedisSerializer
-		redisTemplate.setValueSerializer(fastJsonRedisSerializer);
-		redisTemplate.setHashValueSerializer(fastJsonRedisSerializer);
-
-		// key的序列化采用StringRedisSerializer
-		redisTemplate.setKeySerializer(new StringRedisSerializer());
-		redisTemplate.setHashKeySerializer(new StringRedisSerializer());
-		redisTemplate.setConnectionFactory(redisConnectionFactory);
-		return redisTemplate;
-	}
-}

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

@@ -0,0 +1,28 @@
+package com.genersoft.iot.vmp.conf.redis;
+
+import com.alibaba.fastjson2.support.spring.data.redis.GenericFastJsonRedisSerializer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+@Configuration
+public class RedisTemplateConfig {
+
+    @Bean
+    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
+        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
+        // 使用fastJson序列化
+        GenericFastJsonRedisSerializer fastJsonRedisSerializer = new GenericFastJsonRedisSerializer();
+        // value值的序列化采用fastJsonRedisSerializer
+        redisTemplate.setValueSerializer(fastJsonRedisSerializer);
+        redisTemplate.setHashValueSerializer(fastJsonRedisSerializer);
+
+        // key的序列化采用StringRedisSerializer
+        redisTemplate.setKeySerializer(new StringRedisSerializer());
+        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
+        redisTemplate.setConnectionFactory(redisConnectionFactory);
+        return redisTemplate;
+    }
+}

+ 9 - 2
src/main/java/com/genersoft/iot/vmp/conf/security/JwtAuthenticationFilter.java

@@ -2,6 +2,8 @@ package com.genersoft.iot.vmp.conf.security;
 
 import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.conf.security.dto.JwtUser;
+import com.genersoft.iot.vmp.storager.dao.dto.Role;
+import com.genersoft.iot.vmp.storager.dao.dto.User;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@@ -38,7 +40,6 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
             return;
         }
         if (!userSetting.isInterfaceAuthentication()) {
-            // 构建UsernamePasswordAuthenticationToken,这里密码为null,是因为提供了正确的JWT,实现自动登录
             UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(null, null, new ArrayList<>() );
             SecurityContextHolder.getContext().setAuthentication(token);
             chain.doFilter(request, response);
@@ -76,7 +77,13 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
         }
 
         // 构建UsernamePasswordAuthenticationToken,这里密码为null,是因为提供了正确的JWT,实现自动登录
-        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, jwtUser.getPassword(), new ArrayList<>() );
+        User user = new User();
+        user.setUsername(jwtUser.getUserName());
+        user.setPassword(jwtUser.getPassword());
+        Role role = new Role();
+        role.setId(jwtUser.getRoleId());
+        user.setRole(role);
+        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user, jwtUser.getPassword(), new ArrayList<>() );
         SecurityContextHolder.getContext().setAuthentication(token);
         chain.doFilter(request, response);
     }

+ 4 - 1
src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java

@@ -37,7 +37,7 @@ public class JwtUtils {
      */
     public static final long expirationTime = 30;
 
-    public static String createToken(String username, String password) {
+    public static String createToken(String username, String password, Integer roleId) {
         try {
             /**
              * “iss” (issuer)  发行人
@@ -64,6 +64,7 @@ public class JwtUtils {
             //添加自定义参数,必须是字符串类型
             claims.setClaim("username", username);
             claims.setClaim("password", password);
+            claims.setClaim("roleId", roleId);
 
             //jws
             JsonWebSignature jws = new JsonWebSignature();
@@ -118,8 +119,10 @@ public class JwtUtils {
 
             String username = (String) claims.getClaimValue("username");
             String password = (String) claims.getClaimValue("password");
+            Long roleId = (Long) claims.getClaimValue("roleId");
             jwtUser.setUserName(username);
             jwtUser.setPassword(password);
+            jwtUser.setRoleId(roleId.intValue());
 
             return jwtUser;
         } catch (InvalidJwtException e) {

+ 0 - 65
src/main/java/com/genersoft/iot/vmp/conf/security/LoginFailureHandler.java

@@ -1,65 +0,0 @@
-package com.genersoft.iot.vmp.conf.security;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.authentication.*;
-import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.web.authentication.AuthenticationFailureHandler;
-import org.springframework.stereotype.Component;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-
-@Component
-public class LoginFailureHandler implements AuthenticationFailureHandler {
-
-    private final static Logger logger = LoggerFactory.getLogger(LoginFailureHandler.class);
-
-    @Autowired
-    private ObjectMapper objectMapper;
-
-    @Override
-    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
-
-        String username = request.getParameter("username");
-        if (e instanceof AccountExpiredException) {
-            // 账号过期
-            logger.info("[登录失败] - 用户[{}]账号过期", username);
-
-        } else if (e instanceof BadCredentialsException) {
-            // 密码错误
-            logger.info("[登录失败] - 用户[{}]密码/SIP服务器ID 错误", username);
-
-        } else if (e instanceof CredentialsExpiredException) {
-            // 密码过期
-            logger.info("[登录失败] - 用户[{}]密码过期", username);
-
-        } else if (e instanceof DisabledException) {
-            // 用户被禁用
-            logger.info("[登录失败] - 用户[{}]被禁用", username);
-
-        } else if (e instanceof LockedException) {
-            // 用户被锁定
-            logger.info("[登录失败] - 用户[{}]被锁定", username);
-
-        } else if (e instanceof InternalAuthenticationServiceException) {
-            // 内部错误
-            logger.error(String.format("[登录失败] - [%s]内部错误", username), e);
-
-        } else {
-            // 其他错误
-            logger.error(String.format("[登录失败] - [%s]其他错误", username), e);
-        }
-        Map<String, Object> map = new HashMap<>();
-        map.put("code","0");
-        map.put("msg","登录失败");
-        response.setContentType("application/json;charset=UTF-8");
-        response.getWriter().write(objectMapper.writeValueAsString(map));
-    }
-}

+ 0 - 36
src/main/java/com/genersoft/iot/vmp/conf/security/LoginSuccessHandler.java

@@ -1,36 +0,0 @@
-package com.genersoft.iot.vmp.conf.security;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
-import org.springframework.stereotype.Component;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-
-/**
- * @author lin
- */
-@Component
-public class LoginSuccessHandler implements AuthenticationSuccessHandler {
-
-    private final static Logger logger = LoggerFactory.getLogger(LoginSuccessHandler.class);
-
-    @Override
-    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
-//        String username = request.getParameter("username");
-//        httpServletResponse.setContentType("application/json;charset=UTF-8");
-//        // 生成JWT,并放置到请求头中
-//        String jwt = JwtUtils.createToken(authentication.getName(), );
-//        httpServletResponse.setHeader(JwtUtils.getHeader(), jwt);
-//        ServletOutputStream outputStream = httpServletResponse.getOutputStream();
-//        outputStream.write(JSON.toJSONString(ErrorCode.SUCCESS).getBytes(StandardCharsets.UTF_8));
-//        outputStream.flush();
-//        outputStream.close();
-
-//        logger.info("[登录成功] - [{}]", username);
-    }
-}

+ 3 - 7
src/main/java/com/genersoft/iot/vmp/conf/security/SecurityUtils.java

@@ -53,14 +53,10 @@ public class SecurityUtils {
         Authentication authentication = getAuthentication();
         if(authentication!=null){
             Object principal = authentication.getPrincipal();
-            if(principal!=null && !"anonymousUser".equals(principal)){
-//                LoginUser user = (LoginUser) authentication.getPrincipal();
+            if(principal!=null && !"anonymousUser".equals(principal.toString())){
 
-                String username = (String) principal;
-                User user = new User();
-                user.setUsername(username);
-                LoginUser loginUser = new LoginUser(user, LocalDateTime.now());
-                return loginUser;
+                User user = (User) principal;
+                return new LoginUser(user, LocalDateTime.now());
             }
         }
         return null;

+ 0 - 10
src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java

@@ -47,16 +47,6 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
      * 登出成功的处理
      */
     @Autowired
-    private LoginFailureHandler loginFailureHandler;
-    /**
-     * 登录成功的处理
-     */
-    @Autowired
-    private LoginSuccessHandler loginSuccessHandler;
-    /**
-     * 登出成功的处理
-     */
-    @Autowired
     private LogoutHandler logoutHandler;
     /**
      * 未登录的处理

+ 10 - 0
src/main/java/com/genersoft/iot/vmp/conf/security/dto/JwtUser.java

@@ -25,6 +25,8 @@ public class JwtUser {
 
     private String password;
 
+    private int roleId;
+
     private TokenStatus status;
 
     public String getUserName() {
@@ -50,4 +52,12 @@ public class JwtUser {
     public void setPassword(String password) {
         this.password = password;
     }
+
+    public int getRoleId() {
+        return roleId;
+    }
+
+    public void setRoleId(int roleId) {
+        this.roleId = roleId;
+    }
 }

+ 2 - 9
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java

@@ -36,8 +36,6 @@ public class SipLayer implements CommandLineRunner {
 	private final Map<String, SipProviderImpl> tcpSipProviderMap = new ConcurrentHashMap<>();
 	private final Map<String, SipProviderImpl> udpSipProviderMap = new ConcurrentHashMap<>();
 
-	private SipFactory sipFactory;
-
 	@Override
 	public void run(String... args) {
 		List<String> monitorIps = new ArrayList<>();
@@ -50,8 +48,7 @@ public class SipLayer implements CommandLineRunner {
 			monitorIps.add(sipConfig.getIp());
 		}
 
-		sipFactory = SipFactory.getInstance();
-		sipFactory.setPathName("gov.nist");
+		SipFactory.getInstance().setPathName("gov.nist");
 		if (monitorIps.size() > 0) {
 			for (String monitorIp : monitorIps) {
 				addListeningPoint(monitorIp, sipConfig.getPort());
@@ -65,7 +62,7 @@ public class SipLayer implements CommandLineRunner {
 	private void addListeningPoint(String monitorIp, int port){
 		SipStackImpl sipStack;
 		try {
-			sipStack = (SipStackImpl)sipFactory.createSipStack(DefaultProperties.getProperties(monitorIp, userSetting.getSipLog()));
+			sipStack = (SipStackImpl)SipFactory.getInstance().createSipStack(DefaultProperties.getProperties(monitorIp, userSetting.getSipLog()));
 		} catch (PeerUnavailableException e) {
 			logger.error("[Sip Server] SIP服务启动失败, 监听地址{}失败,请检查ip是否正确", monitorIp);
 			return;
@@ -106,10 +103,6 @@ public class SipLayer implements CommandLineRunner {
 		}
 	}
 
-	public SipFactory getSipFactory() {
-		return sipFactory;
-	}
-
 	public SipProviderImpl getUdpSipProvider(String ip) {
 		if (ObjectUtils.isEmpty(ip)) {
 			return null;

+ 0 - 27
src/main/java/com/genersoft/iot/vmp/gb28181/bean/CmdSendFailEvent.java

@@ -1,27 +0,0 @@
-package com.genersoft.iot.vmp.gb28181.bean;
-
-import javax.sip.Dialog;
-import java.util.EventObject;
-
-public class CmdSendFailEvent extends EventObject {
-
-    private String callId;
-
-    /**
-     * Constructs a prototypical Event.
-     *
-     * @param dialog
-     * @throws IllegalArgumentException if source is null.
-     */
-    public CmdSendFailEvent(Dialog dialog) {
-        super(dialog);
-    }
-
-    public String getCallId() {
-        return callId;
-    }
-
-    public void setCallId(String callId) {
-        this.callId = callId;
-    }
-}

+ 11 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java

@@ -247,6 +247,17 @@ public class Device {
 		return streamMode;
 	}
 
+	public Integer getStreamModeForParam() {
+		if (streamMode.equalsIgnoreCase("UDP")) {
+			return 0;
+		}else if (streamMode.equalsIgnoreCase("TCP-PASSIVE")) {
+			return 1;
+		}else if (streamMode.equalsIgnoreCase("TCP-ACTIVE")) {
+			return 2;
+		}
+		return 0;
+	}
+
 	public void setStreamMode(String streamMode) {
 		this.streamMode = streamMode;
 	}

+ 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,PUSH,PROXY,CLOUD_RECORD_PUSH,CLOUD_RECORD_PROXY,BROADCAST,TALK
+    PLAY,PLAYBACK,DOWNLOAD,PUSH,PROXY,CLOUD_RECORD_PUSH,CLOUD_RECORD_PROXY,BROADCAST,TALK
 
 
 }

+ 4 - 4
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SsrcTransaction.java

@@ -1,6 +1,6 @@
 package com.genersoft.iot.vmp.gb28181.bean;
 
-import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
+import com.genersoft.iot.vmp.common.InviteSessionType;
 
 public class SsrcTransaction {
 
@@ -13,7 +13,7 @@ public class SsrcTransaction {
 
     private SipTransactionInfo sipTransactionInfo;
 
-    private VideoStreamSessionManager.SessionType type;
+    private InviteSessionType type;
 
     public String getDeviceId() {
         return deviceId;
@@ -63,11 +63,11 @@ public class SsrcTransaction {
         this.ssrc = ssrc;
     }
 
-    public VideoStreamSessionManager.SessionType getType() {
+    public InviteSessionType getType() {
         return type;
     }
 
-    public void setType(VideoStreamSessionManager.SessionType type) {
+    public void setType(InviteSessionType type) {
         this.type = type;
     }
 

+ 3 - 5
src/main/java/com/genersoft/iot/vmp/gb28181/conf/ServerLoggerImpl.java

@@ -27,7 +27,7 @@ public class ServerLoggerImpl implements ServerLogger {
             return;
         }
         StringBuilder stringBuilder = new StringBuilder();
-        stringBuilder.append(!sender? "发送:目标--->" + from:"接收:来自--->" + to)
+        stringBuilder.append(sender? "发送:目标--->" + from:"接收:来自--->" + to)
                 .append("\r\n")
                         .append(message);
         this.stackLogger.logInfo(stringBuilder.toString());
@@ -40,7 +40,7 @@ public class ServerLoggerImpl implements ServerLogger {
             return;
         }
         StringBuilder stringBuilder = new StringBuilder();
-        stringBuilder.append(!sender? "发送: 目标->" + from :"接收:来自->" + to)
+        stringBuilder.append(sender? "发送: 目标->" + from :"接收:来自->" + to)
                 .append("\r\n")
                 .append(message);
         this.stackLogger.logInfo(stringBuilder.toString());
@@ -52,7 +52,7 @@ public class ServerLoggerImpl implements ServerLogger {
             return;
         }
         StringBuilder stringBuilder = new StringBuilder();
-        stringBuilder.append(!sender? "发送: 目标->" + from :"接收:来自->" + to)
+        stringBuilder.append(sender? "发送: 目标->" + from :"接收:来自->" + to)
                 .append("\r\n")
                 .append(message);
         this.stackLogger.logInfo(stringBuilder.toString());
@@ -87,6 +87,4 @@ public class ServerLoggerImpl implements ServerLogger {
             this.stackLogger = this.sipStack.getStackLogger();
         }
     }
-
-
 }

+ 6 - 4
src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java

@@ -76,7 +76,11 @@ public class SipSubscribe {
         // 会话已结束
         dialogTerminated,
         // 设备未找到
-        deviceNotFoundEvent
+        deviceNotFoundEvent,
+        // 消息发送失败
+        cmdSendFailEvent,
+        // 消息发送失败
+        failedToGetPort
     }
 
     public static class EventResult<EventObject>{
@@ -86,9 +90,7 @@ public class SipSubscribe {
         public String callId;
         public EventObject event;
 
-        public EventResult(int statusCode, String msg) {
-            this.statusCode = statusCode;
-            this.msg = msg;
+        public EventResult() {
         }
 
         public EventResult(EventObject event) {

+ 10 - 7
src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java

@@ -1,14 +1,9 @@
 package com.genersoft.iot.vmp.gb28181.event.subscribe.catalog;
 
-import com.genersoft.iot.vmp.common.VideoManagerConstants;
-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.transmit.cmd.impl.SIPCommanderFroPlatform;
-import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
 import com.genersoft.iot.vmp.service.IGbStreamService;
-import com.genersoft.iot.vmp.service.IMediaServerService;
-import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -16,12 +11,14 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationListener;
 import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
 
 import javax.sip.InvalidArgumentException;
 import javax.sip.SipException;
 import java.text.ParseException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 /**
  * catalog事件
@@ -43,6 +40,9 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
     @Autowired
     private SubscribeHolder subscribeHolder;
 
+    @Autowired
+    private UserSetting userSetting;
+
     @Override
     public void onApplicationEvent(CatalogEvent event) {
         SubscribeInfo subscribe = null;
@@ -93,6 +93,9 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
                     }
                     if (event.getGbStreams() != null && event.getGbStreams().size() > 0){
                         for (GbStream gbStream : event.getGbStreams()) {
+                            if (gbStream.getStreamType().equals("push") && !userSetting.isUsePushingAsStatus()) {
+                                continue;
+                            }
                             DeviceChannel deviceChannelByStream = gbStreamService.getDeviceChannelListByStream(gbStream, gbStream.getCatalogId(), parentPlatform);
                             deviceChannelList.add(deviceChannelByStream);
                         }

+ 132 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/session/SSRCFactory.java

@@ -0,0 +1,132 @@
+package com.genersoft.iot.vmp.gb28181.session;
+
+import com.genersoft.iot.vmp.conf.SipConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * ssrc使用
+ */
+@Component
+public class SSRCFactory {
+
+    /**
+     * 播流最大并发个数
+     */
+    private static final Integer MAX_STREAM_COUNT = 10000;
+
+    /**
+     * 播流最大并发个数
+     */
+    private static final String SSRC_INFO_KEY = "VMP_SSRC_INFO_";
+
+    @Autowired
+    private StringRedisTemplate redisTemplate;
+
+    @Autowired
+    private SipConfig sipConfig;
+
+
+    public void initMediaServerSSRC(String mediaServerId, Set<String> usedSet) {
+        String ssrcPrefix = sipConfig.getDomain().substring(3, 8);
+        String redisKey = SSRC_INFO_KEY + mediaServerId;
+        List<String> ssrcList = new ArrayList<>();
+        for (int i = 1; i < MAX_STREAM_COUNT; i++) {
+            String ssrc = String.format("%s%04d", ssrcPrefix, i);
+
+            if (null == usedSet || !usedSet.contains(ssrc)) {
+                ssrcList.add(ssrc);
+
+            }
+        }
+        if (redisTemplate.opsForSet().size(redisKey) != null) {
+            redisTemplate.delete(redisKey);
+        }
+        redisTemplate.opsForSet().add(redisKey, ssrcList.toArray(new String[0]));
+    }
+
+
+    /**
+     * 获取视频预览的SSRC值,第一位固定为0
+     *
+     * @return ssrc
+     */
+    public String getPlaySsrc(String mediaServerId) {
+        return "0" + getSN(mediaServerId);
+    }
+
+    /**
+     * 获取录像回放的SSRC值,第一位固定为1
+     */
+    public String getPlayBackSsrc(String mediaServerId) {
+        return "1" + getSN(mediaServerId);
+    }
+
+    /**
+     * 释放ssrc,主要用完的ssrc一定要释放,否则会耗尽
+     *
+     * @param ssrc 需要重置的ssrc
+     */
+    public void releaseSsrc(String mediaServerId, String ssrc) {
+        if (ssrc == null) {
+            return;
+        }
+        String sn = ssrc.substring(1);
+        String redisKey = SSRC_INFO_KEY + mediaServerId;
+        redisTemplate.opsForSet().add(redisKey, sn);
+    }
+
+    /**
+     * 获取后四位数SN,随机数
+     */
+    private String getSN(String mediaServerId) {
+        String sn = null;
+        String redisKey = SSRC_INFO_KEY + mediaServerId;
+        Long size = redisTemplate.opsForSet().size(redisKey);
+        if (size == null || size == 0) {
+            throw new RuntimeException("ssrc已经用完");
+        } else {
+            // 在集合中移除并返回一个随机成员。
+            sn = (String) redisTemplate.opsForSet().pop(redisKey);
+            redisTemplate.opsForSet().remove(redisKey, sn);
+        }
+        return sn;
+    }
+
+    /**
+     * 重置一个流媒体服务的所有ssrc
+     *
+     * @param mediaServerId 流媒体服务ID
+     */
+    public void reset(String mediaServerId) {
+        this.initMediaServerSSRC(mediaServerId, null);
+    }
+
+    /**
+     * 是否已经存在了某个MediaServer的SSRC信息
+     *
+     * @param mediaServerId 流媒体服务ID
+     */
+    public boolean hasMediaServerSSRC(String mediaServerId) {
+        String redisKey = SSRC_INFO_KEY + mediaServerId;
+        return redisTemplate.opsForSet().members(redisKey) != null;
+    }
+
+    /**
+     * 查询ssrc是否可用
+     *
+     * @param mediaServerId
+     * @param ssrc
+     * @return
+     */
+    public boolean checkSsrc(String mediaServerId, String ssrc) {
+        String sn = ssrc.substring(1);
+        String redisKey = SSRC_INFO_KEY + mediaServerId;
+        return redisTemplate.opsForSet().isMember(redisKey, sn) != null;
+    }
+}

+ 0 - 150
src/main/java/com/genersoft/iot/vmp/gb28181/session/SsrcConfig.java

@@ -1,150 +0,0 @@
-package com.genersoft.iot.vmp.gb28181.session;
-
-import com.genersoft.iot.vmp.utils.ConfigConst;
-import io.swagger.v3.oas.annotations.media.Schema;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
-import java.util.Set;
-
-@Schema(description = "ssrc信息")
-public class SsrcConfig {
-
-    /**
-     * zlm流媒体服务器Id
-     */
-    @Schema(description = "流媒体服务器Id")
-    private String mediaServerId;
-
-    @Schema(description = "SSRC前缀")
-    private String ssrcPrefix;
-
-    /**
-     * zlm流媒体服务器已用会话句柄
-     */
-    @Schema(description = "zlm流媒体服务器已用会话句柄")
-    private List<String> isUsed;
-
-    /**
-     * zlm流媒体服务器可用会话句柄
-     */
-    @Schema(description = "zlm流媒体服务器可用会话句柄")
-    private List<String> notUsed;
-
-    public SsrcConfig() {
-    }
-
-    public SsrcConfig(String mediaServerId, Set<String> usedSet, String sipDomain) {
-        this.mediaServerId = mediaServerId;
-        this.isUsed = new ArrayList<>();
-        this.ssrcPrefix = sipDomain.substring(3, 8);
-        this.notUsed = new ArrayList<>();
-        for (int i = 1; i < ConfigConst.MAX_STRTEAM_COUNT; i++) {
-            String ssrc;
-            if (i < 10) {
-                ssrc = "000" + i;
-            } else if (i < 100) {
-                ssrc = "00" + i;
-            } else if (i < 1000) {
-                ssrc = "0" + i;
-            } else {
-                ssrc = String.valueOf(i);
-            }
-            if (null == usedSet || !usedSet.contains(ssrc)) {
-                this.notUsed.add(ssrc);
-            } else {
-                this.isUsed.add(ssrc);
-            }
-        }
-    }
-
-
-    /**
-     * 获取视频预览的SSRC值,第一位固定为0
-     * @return ssrc
-     */
-    public String getPlaySsrc() {
-        return "0" + getSsrcPrefix() + getSN();
-    }
-
-    /**
-     * 获取录像回放的SSRC值,第一位固定为1
-     *
-     */
-    public String getPlayBackSsrc() {
-        return "1" + getSsrcPrefix() + getSN();
-    }
-
-    /**
-     * 释放ssrc,主要用完的ssrc一定要释放,否则会耗尽
-     * @param ssrc 需要重置的ssrc
-     */
-    public void releaseSsrc(String ssrc) {
-        if (ssrc == null) {
-            return;
-        }
-        String sn = ssrc.substring(6);
-        try {
-            isUsed.remove(sn);
-            notUsed.add(sn);
-        }catch (NullPointerException e){
-        }
-    }
-
-    /**
-     * 获取后四位数SN,随机数
-     *
-     */
-    private String getSN() {
-        String sn = null;
-        int index = 0;
-        if (notUsed.size() == 0) {
-            throw new RuntimeException("ssrc已经用完");
-        } else if (notUsed.size() == 1) {
-            sn = notUsed.get(0);
-        } else {
-            index = new Random().nextInt(notUsed.size() - 1);
-            sn = notUsed.get(index);
-        }
-        notUsed.remove(index);
-        isUsed.add(sn);
-        return sn;
-    }
-
-    public String getSsrcPrefix() {
-        return ssrcPrefix;
-    }
-
-    public String getMediaServerId() {
-        return mediaServerId;
-    }
-
-    public void setMediaServerId(String mediaServerId) {
-        this.mediaServerId = mediaServerId;
-    }
-
-    public void setSsrcPrefix(String ssrcPrefix) {
-        this.ssrcPrefix = ssrcPrefix;
-    }
-
-    public List<String> getIsUsed() {
-        return isUsed;
-    }
-
-    public void setIsUsed(List<String> isUsed) {
-        this.isUsed = isUsed;
-    }
-
-    public List<String> getNotUsed() {
-        return notUsed;
-    }
-
-    public void setNotUsed(List<String> notUsed) {
-        this.notUsed = notUsed;
-    }
-
-    public boolean checkSsrc(String ssrcInResponse) {
-        return !isUsed.contains(ssrcInResponse);
-    }
-}

+ 2 - 9
src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java

@@ -1,5 +1,6 @@
 package com.genersoft.iot.vmp.gb28181.session;
 
+import com.genersoft.iot.vmp.common.InviteSessionType;
 import com.genersoft.iot.vmp.common.VideoManagerConstants;
 import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
@@ -27,14 +28,6 @@ public class VideoStreamSessionManager {
 	@Autowired
 	private RedisTemplate<Object, Object> redisTemplate;
 
-	public enum SessionType {
-		play,
-		playback,
-		download,
-		broadcast,
-		talk
-	}
-
 	/**
 	 * 添加一个点播/回放的事务信息
 	 * 后续可以通过流Id/callID
@@ -45,7 +38,7 @@ public class VideoStreamSessionManager {
 	 * @param mediaServerId 所使用的流媒体ID
 	 * @param response 回复
 	 */
-	public void put(String deviceId, String channelId, String callId, String stream, String ssrc, String mediaServerId, SIPResponse response, SessionType type){
+	public void put(String deviceId, String channelId, String callId, String stream, String ssrc, String mediaServerId, SIPResponse response, InviteSessionType type){
 		SsrcTransaction ssrcTransaction = new SsrcTransaction();
 		ssrcTransaction.setDeviceId(deviceId);
 		ssrcTransaction.setChannelId(channelId);

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

@@ -5,6 +5,7 @@ import com.genersoft.iot.vmp.conf.UserSetting;
 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.session.SSRCFactory;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
@@ -37,6 +38,9 @@ public class SipRunner implements CommandLineRunner {
     @Autowired
     private IRedisCatchStorage redisCatchStorage;
 
+    @Autowired
+    private SSRCFactory ssrcFactory;
+
     @Autowired
     private UserSetting userSetting;
 
@@ -96,6 +100,7 @@ public class SipRunner implements CommandLineRunner {
                 MediaServerItem mediaServerItem = mediaServerService.getOne(sendRtpItem.getMediaServerId());
                 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());

+ 2 - 6
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPSender.java

@@ -46,8 +46,7 @@ public class SIPSender {
         transmitRequest(ip, message, errorEvent, null);
     }
 
-    public void transmitRequest(String ip, Message message, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, ParseException {
-        try {
+    public void transmitRequest(String ip, Message message, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException {
             ViaHeader viaHeader = (ViaHeader)message.getHeader(ViaHeader.NAME);
             String transport = "UDP";
             if (viaHeader == null) {
@@ -57,7 +56,7 @@ public class SIPSender {
             }
             if (message.getHeader(UserAgentHeader.NAME) == null) {
                 try {
-                    message.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
+                    message.addHeader(SipUtils.createUserAgentHeader(gitUtil));
                 } catch (ParseException e) {
                     logger.error("添加UserAgentHeader失败", e);
                 }
@@ -104,9 +103,6 @@ public class SIPSender {
                     sipProvider.sendResponse((Response)message);
                 }
             }
-        } finally {
-//            logger.info("[SEND]:SUCCESS:{}", message);
-        }
     }
 
     public CallIdHeader getNewCallIdHeader(String ip, String transport){

+ 4 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java

@@ -3,6 +3,8 @@ 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.event.SipSubscribe;
 import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
@@ -107,7 +109,7 @@ public interface ISIPCommander {
 	 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
 	 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
 	 */
-	void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInf, Device device, String channelId, String startTime, String endTime,InviteStreamCallback inviteStreamCallback, InviteStreamCallback event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+	void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInf, Device device, String channelId, String startTime, String endTime,ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
 
 	/**
 	 * 请求历史媒体下载
@@ -119,7 +121,7 @@ public interface ISIPCommander {
 	 * @param downloadSpeed 下载倍速参数
 	 */ 
 	void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
-						   String startTime, String endTime, int downloadSpeed, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
+						   String startTime, String endTime, int downloadSpeed, ZlmHttpHookSubscribe.Event hookEvent,
 						   SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
 
 

+ 99 - 98
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java

@@ -17,6 +17,7 @@ import org.springframework.util.DigestUtils;
 
 import javax.sip.InvalidArgumentException;
 import javax.sip.PeerUnavailableException;
+import javax.sip.SipFactory;
 import javax.sip.address.Address;
 import javax.sip.address.SipURI;
 import javax.sip.header.*;
@@ -50,39 +51,39 @@ public class SIPRequestHeaderPlarformProvider {
 		Request request = null;
 		String sipAddress = parentPlatform.getDeviceIp() + ":" + parentPlatform.getDevicePort();
 		//请求行
-		SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(),
+		SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(),
 				parentPlatform.getServerIP() + ":" + parentPlatform.getServerPort());
 		//via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getServerIP(),
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(parentPlatform.getServerIP(),
 				parentPlatform.getServerPort(), parentPlatform.getTransport(), SipUtils.getNewViaTag());
 		viaHeader.setRPort();
 		viaHeaders.add(viaHeader);
 		//from
-		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), sipConfig.getDomain());
-		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, fromTag);
+		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), sipConfig.getDomain());
+		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, fromTag);
 		//to
-		SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), sipConfig.getDomain());
-		Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress,toTag);
+		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), sipConfig.getDomain());
+		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,toTag);
 
 		//Forwards
-		MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
 
 		//ceq
-		CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(CSeq, Request.REGISTER);
-		request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestLine, Request.REGISTER, callIdHeader,
+		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(CSeq, Request.REGISTER);
+		request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.REGISTER, callIdHeader,
 				cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
 
-		Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory()
+		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory()
 				.createSipURI(parentPlatform.getDeviceGBId(), sipAddress));
-		request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress));
+		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
 
-		ExpiresHeader expiresHeader = sipLayer.getSipFactory().createHeaderFactory().createExpiresHeader(expires);
+		ExpiresHeader expiresHeader = SipFactory.getInstance().createHeaderFactory().createExpiresHeader(expires);
 		request.addHeader(expiresHeader);
 
-		request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
 
 		return request;
 	}
@@ -92,9 +93,9 @@ public class SIPRequestHeaderPlarformProvider {
 
 
 		Request registerRequest = createRegisterRequest(parentPlatform, redisCatchStorage.getCSEQ(), fromTag, toTag, callIdHeader, expires);
-		SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP() + ":" + parentPlatform.getServerPort());
+		SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP() + ":" + parentPlatform.getServerPort());
 		if (www == null) {
-			AuthorizationHeader authorizationHeader = sipLayer.getSipFactory().createHeaderFactory().createAuthorizationHeader("Digest");
+			AuthorizationHeader authorizationHeader = SipFactory.getInstance().createHeaderFactory().createAuthorizationHeader("Digest");
 			String username = parentPlatform.getUsername();
 			if ( username == null || username == "" )
 			{
@@ -147,7 +148,7 @@ public class SIPRequestHeaderPlarformProvider {
 
 		String RESPONSE = DigestUtils.md5DigestAsHex(reStr.toString().getBytes());
 
-		AuthorizationHeader authorizationHeader = sipLayer.getSipFactory().createHeaderFactory().createAuthorizationHeader(scheme);
+		AuthorizationHeader authorizationHeader = SipFactory.getInstance().createHeaderFactory().createAuthorizationHeader(scheme);
 		authorizationHeader.setUsername(parentPlatform.getDeviceGBId());
 		authorizationHeader.setRealm(realm);
 		authorizationHeader.setNonce(nonce);
@@ -165,7 +166,7 @@ public class SIPRequestHeaderPlarformProvider {
 	}
 
 	public Request createMessageRequest(ParentPlatform parentPlatform, String content, SendRtpItem sendRtpItem) throws PeerUnavailableException, ParseException, InvalidArgumentException {
-		CallIdHeader callIdHeader = sipLayer.getSipFactory().createHeaderFactory().createCallIdHeader(sendRtpItem.getCallId());
+		CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(sendRtpItem.getCallId());
 		return createMessageRequest(parentPlatform, content, sendRtpItem.getToTag(), SipUtils.getNewViaTag(), sendRtpItem.getFromTag(), callIdHeader);
 	}
 
@@ -178,36 +179,36 @@ public class SIPRequestHeaderPlarformProvider {
 		Request request = null;
 		String serverAddress = parentPlatform.getServerIP()+ ":" + parentPlatform.getServerPort();
 		// sipuri
-		SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), serverAddress);
+		SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), serverAddress);
 		// via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), parentPlatform.getDevicePort(),
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), parentPlatform.getDevicePort(),
 				parentPlatform.getTransport(), viaTag);
 		viaHeader.setRPort();
 		viaHeaders.add(viaHeader);
 		// from
-		// SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), parentPlatform.getDeviceIp() + ":" + parentPlatform.getDeviceIp());
-		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), sipConfig.getDomain());
-		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, fromTag);
+		// SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), parentPlatform.getDeviceIp() + ":" + parentPlatform.getDeviceIp());
+		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), sipConfig.getDomain());
+		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, fromTag);
 		// to
-		SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), serverAddress);
-		Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress, toTag);
+		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), serverAddress);
+		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, toTag);
 
 		// Forwards
-		MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
 		// ceq
-		CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.MESSAGE);
-		MessageFactoryImpl messageFactory = (MessageFactoryImpl) sipLayer.getSipFactory().createMessageFactory();
+		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.MESSAGE);
+		MessageFactoryImpl messageFactory = (MessageFactoryImpl) SipFactory.getInstance().createMessageFactory();
 		// 设置编码, 防止中文乱码
 		messageFactory.setDefaultContentEncodingCharset(parentPlatform.getCharacterSet());
 		request = messageFactory.createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader,
 				toHeader, viaHeaders, maxForwards);
 
-		request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
 
-		ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
+		ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
 		request.setContent(content, contentTypeHeader);
 		return request;
 	}
@@ -215,54 +216,54 @@ public class SIPRequestHeaderPlarformProvider {
 	public SIPRequest createNotifyRequest(ParentPlatform parentPlatform, String content, SubscribeInfo subscribeInfo) throws PeerUnavailableException, ParseException, InvalidArgumentException {
 		SIPRequest request = null;
 		// sipuri
-		SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP()+ ":" + parentPlatform.getServerPort());
+		SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP()+ ":" + parentPlatform.getServerPort());
 		// via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<>();
-		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), parentPlatform.getDevicePort(),
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), parentPlatform.getDevicePort(),
 				parentPlatform.getTransport(), SipUtils.getNewViaTag());
 		viaHeader.setRPort();
 		viaHeaders.add(viaHeader);
 		// from
-		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(),
+		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(),
 				parentPlatform.getDeviceIp() + ":" + parentPlatform.getDevicePort());
-		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, subscribeInfo.getResponse().getToTag());
+		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, subscribeInfo.getResponse().getToTag());
 		// to
-		SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerGBDomain());
-		Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress, subscribeInfo.getRequest().getFromTag());
+		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerGBDomain());
+		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, subscribeInfo.getRequest().getFromTag());
 
 		// Forwards
-		MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
 		// ceq
-		CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.NOTIFY);
-		MessageFactoryImpl messageFactory = (MessageFactoryImpl) sipLayer.getSipFactory().createMessageFactory();
+		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.NOTIFY);
+		MessageFactoryImpl messageFactory = (MessageFactoryImpl) SipFactory.getInstance().createMessageFactory();
 		// 设置编码, 防止中文乱码
 		messageFactory.setDefaultContentEncodingCharset("gb2312");
 
-		CallIdHeader callIdHeader = sipLayer.getSipFactory().createHeaderFactory().createCallIdHeader(subscribeInfo.getRequest().getCallIdHeader().getCallId());
+		CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(subscribeInfo.getRequest().getCallIdHeader().getCallId());
 
 		request = (SIPRequest) messageFactory.createRequest(requestURI, Request.NOTIFY, callIdHeader, cSeqHeader, fromHeader,
 				toHeader, viaHeaders, maxForwards);
 
-		request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
 
-		EventHeader event = sipLayer.getSipFactory().createHeaderFactory().createEventHeader(subscribeInfo.getEventType());
+		EventHeader event = SipFactory.getInstance().createHeaderFactory().createEventHeader(subscribeInfo.getEventType());
 		if (subscribeInfo.getEventId() != null) {
 			event.setEventId(subscribeInfo.getEventId());
 		}
 
 		request.addHeader(event);
 
-		SubscriptionStateHeader active = sipLayer.getSipFactory().createHeaderFactory().createSubscriptionStateHeader("active");
+		SubscriptionStateHeader active = SipFactory.getInstance().createHeaderFactory().createSubscriptionStateHeader("active");
 		request.setHeader(active);
 
 		String sipAddress = parentPlatform.getDeviceIp() + ":" + parentPlatform.getDevicePort();
-		Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory()
+		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory()
 				.createSipURI(parentPlatform.getDeviceGBId(), sipAddress));
-		request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress));
+		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
 
-		ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
+		ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
 		request.setContent(content, contentTypeHeader);
 		return request;
     }
@@ -275,42 +276,42 @@ public class SIPRequestHeaderPlarformProvider {
 
 		SIPRequest request = null;
 		// sipuri
-		SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(platform.getServerGBId(), platform.getServerIP()+ ":" + platform.getServerPort());
+		SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(platform.getServerGBId(), platform.getServerIP()+ ":" + platform.getServerPort());
 		// via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<>();
-		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(platform.getDeviceIp(), platform.getDevicePort(),
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(platform.getDeviceIp(), platform.getDevicePort(),
 				platform.getTransport(), SipUtils.getNewViaTag());
 		viaHeader.setRPort();
 		viaHeaders.add(viaHeader);
 		// from
-		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(platform.getDeviceGBId(),
+		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(platform.getDeviceGBId(),
 				platform.getDeviceIp() + ":" + platform.getDevicePort());
-		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, sendRtpItem.getToTag());
+		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, sendRtpItem.getToTag());
 		// to
-		SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(platform.getServerGBId(), platform.getServerGBDomain());
-		Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress, sendRtpItem.getFromTag());
+		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(platform.getServerGBId(), platform.getServerGBDomain());
+		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, sendRtpItem.getFromTag());
 
 		// Forwards
-		MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
 		// ceq
-		CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE);
-		MessageFactoryImpl messageFactory = (MessageFactoryImpl) sipLayer.getSipFactory().createMessageFactory();
+		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE);
+		MessageFactoryImpl messageFactory = (MessageFactoryImpl) SipFactory.getInstance().createMessageFactory();
 		// 设置编码, 防止中文乱码
 		messageFactory.setDefaultContentEncodingCharset("gb2312");
 
-		CallIdHeader callIdHeader = sipLayer.getSipFactory().createHeaderFactory().createCallIdHeader(sendRtpItem.getCallId());
+		CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(sendRtpItem.getCallId());
 
 		request = (SIPRequest) messageFactory.createRequest(requestURI, Request.BYE, callIdHeader, cSeqHeader, fromHeader,
 				toHeader, viaHeaders, maxForwards);
 
-		request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
 
 		String sipAddress = platform.getDeviceIp() + ":" + platform.getDevicePort();
-		Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory()
+		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory()
 				.createSipURI(platform.getDeviceGBId(), sipAddress));
-		request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress));
+		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
 
 		return request;
 	}
@@ -320,37 +321,37 @@ public class SIPRequestHeaderPlarformProvider {
 		//请求行
 		String platformHostAddress = platform.getServerIP() + ":" + platform.getServerPort();
 		String localHostAddress = sipLayer.getLocalIp(platform.getDeviceIp())+":"+ platform.getDevicePort();
-		SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, platformHostAddress);
+		SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, platformHostAddress);
 		//via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(platform.getDeviceIp()), platform.getDevicePort(), platform.getTransport(), viaTag);
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(platform.getDeviceIp()), platform.getDevicePort(), platform.getTransport(), viaTag);
 		viaHeader.setRPort();
 		viaHeaders.add(viaHeader);
 
 		//from
-		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(platform.getDeviceGBId(), sipConfig.getDomain());
-		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack
+		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 = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, platformHostAddress);
-		Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress,null);
+		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 = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
 
 		//ceq
-		CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE);
-		request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
+		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(sipLayer.getSipFactory(), gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
 
-		Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(),localHostAddress));
-		request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress));
+		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(),localHostAddress));
+		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
 		// Subject
-		SubjectHeader subjectHeader = sipLayer.getSipFactory().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0));
+		SubjectHeader subjectHeader = SipFactory.getInstance().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0));
 		request.addHeader(subjectHeader);
-		ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
+		ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
 		request.setContent(content, contentTypeHeader);
 		return request;
     }
@@ -358,35 +359,35 @@ public class SIPRequestHeaderPlarformProvider {
 	public Request createByteRequest(ParentPlatform platform, String channelId, SipTransactionInfo transactionInfo) throws PeerUnavailableException, ParseException, InvalidArgumentException {
 		String deviceHostAddress = platform.getDeviceIp() + ":" + platform.getDevicePort();
 		Request request = null;
-		SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, deviceHostAddress);
+		SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, deviceHostAddress);
 
 		// via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(platform.getDeviceIp()), platform.getDevicePort(), platform.getTransport(), SipUtils.getNewViaTag());
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(platform.getDeviceIp()), platform.getDevicePort(), platform.getTransport(), SipUtils.getNewViaTag());
 		viaHeaders.add(viaHeader);
 		//from
-		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain());
-		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.isAsSender()?transactionInfo.getFromTag():transactionInfo.getToTag());
+		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 = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, deviceHostAddress);
-		Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress,transactionInfo.isAsSender()?transactionInfo.getToTag():transactionInfo.getFromTag());
+		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 = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
 
 		//ceq
-		CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE);
-		CallIdHeader callIdHeader = sipLayer.getSipFactory().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId());
-		request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestLine, Request.BYE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
+		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(sipLayer.getSipFactory(), gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
 
-		Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(platform.getDeviceIp())+":"+ platform.getDevicePort()));
-		request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress));
+		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(sipLayer.getSipFactory(), gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
 
 		return request;
 	}

+ 123 - 118
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java

@@ -16,6 +16,7 @@ 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.*;
@@ -49,32 +50,32 @@ public class SIPRequestHeaderProvider {
 	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 = sipLayer.getSipFactory().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
+		SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
 		// via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag);
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag);
 		viaHeader.setRPort();
 		viaHeaders.add(viaHeader);
 		// from
-		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
-		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, fromTag);
+		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 = sipLayer.getSipFactory().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
-		Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress, toTag);
+		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 = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
 		// ceq
-		CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.MESSAGE);
+		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.MESSAGE);
 
-		request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader,
+		request = SipFactory.getInstance().createMessageFactory().createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader,
 				toHeader, viaHeaders, maxForwards);
 
-		request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
 
-		ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
+		ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
 		request.setContent(content, contentTypeHeader);
 		return request;
 	}
@@ -82,39 +83,39 @@ public class SIPRequestHeaderProvider {
 	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 = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress());
+		SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
 		//via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		HeaderFactory headerFactory = sipLayer.getSipFactory().createHeaderFactory();
-		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag);
+		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 = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
-		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack
+		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 = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress());
-		Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress,null);
+		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 = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
 		
 		//ceq
-		CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE);
-		request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
+		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(sipLayer.getSipFactory(), gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
 
-		Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
-		// Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), device.getHost().getIp()+":"+device.getHost().getPort()));
-		request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress));
+		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 = sipLayer.getSipFactory().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0));
+		SubjectHeader subjectHeader = SipFactory.getInstance().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0));
 		request.addHeader(subjectHeader);
-		ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
+		ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
 		request.setContent(content, contentTypeHeader);
 		return request;
 	}
@@ -122,69 +123,74 @@ public class SIPRequestHeaderProvider {
 	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 = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress());
+		SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
 		// via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag);
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag);
 		viaHeader.setRPort();
 		viaHeaders.add(viaHeader);
 		//from
-		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
-		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack
+		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 = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress());
-		Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress,null);
+		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 = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
 		
 		//ceq
-		CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE);
-		request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
+		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 = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
-		// Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), device.getHost().getIp()+":"+device.getHost().getPort()));
-		request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress));
+		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(sipLayer.getSipFactory(), gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
 
 		// Subject
-		SubjectHeader subjectHeader = sipLayer.getSipFactory().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0));
+		SubjectHeader subjectHeader = SipFactory.getInstance().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0));
 		request.addHeader(subjectHeader);
 
-		ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
+		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 = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress());
-
+		//请求行
+		SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
 		// via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag());
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag());
 		viaHeaders.add(viaHeader);
 		//from
-		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain());
-		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, !transactionInfo.isAsSender()? transactionInfo.getToTag():transactionInfo.getFromTag());
+		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 = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId,device.getHostAddress());
-		Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress, !transactionInfo.isAsSender()? transactionInfo.getToTag(): transactionInfo.getFromTag());
+		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 = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
 
 		//ceq
-		CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE);
-		CallIdHeader callIdHeader = sipLayer.getSipFactory().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId());
-		request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestLine, Request.BYE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
+		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));
 
-		request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), 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;
 	}
@@ -192,50 +198,50 @@ public class SIPRequestHeaderProvider {
 	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 = sipLayer.getSipFactory().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
+		SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
 		// via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(),
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(),
 				device.getTransport(), SipUtils.getNewViaTag());
 		viaHeader.setRPort();
 		viaHeaders.add(viaHeader);
 		// from
-		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
-		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, requestOld == null ? SipUtils.getNewFromTag() :requestOld.getFromTag());
+		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 = sipLayer.getSipFactory().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
-		Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress, requestOld == null ? null :requestOld.getToTag());
+		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 = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
 
 		// ceq
-		CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.SUBSCRIBE);
+		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.SUBSCRIBE);
 
-		request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestURI, Request.SUBSCRIBE, callIdHeader, cSeqHeader, fromHeader,
+		request = SipFactory.getInstance().createMessageFactory().createRequest(requestURI, Request.SUBSCRIBE, callIdHeader, cSeqHeader, fromHeader,
 				toHeader, viaHeaders, maxForwards);
 
 
-		Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
-		request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress));
+		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 = sipLayer.getSipFactory().createHeaderFactory().createExpiresHeader(expires);
+		ExpiresHeader expireHeader = SipFactory.getInstance().createHeaderFactory().createExpiresHeader(expires);
 		request.addHeader(expireHeader);
 
 		// Event
-		EventHeader eventHeader = sipLayer.getSipFactory().createHeaderFactory().createEventHeader(event);
+		EventHeader eventHeader = SipFactory.getInstance().createHeaderFactory().createEventHeader(event);
 
 		int random = (int) Math.floor(Math.random() * 10000);
 		eventHeader.setEventId(random + "");
 		request.addHeader(eventHeader);
 
-		ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
+		ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
 		request.setContent(content, contentTypeHeader);
 
-		request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
 
 		return request;
 	}
@@ -247,37 +253,37 @@ public class SIPRequestHeaderProvider {
 		}
 		SIPRequest request = null;
 		//请求行
-		SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress());
+		SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
 		// via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag());
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag());
 		viaHeaders.add(viaHeader);
 		//from
-		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain());
-		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.getFromTag());
+		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 = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId,device.getHostAddress());
-		Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress,	transactionInfo.getToTag());
+		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 = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
 
 		//ceq
-		CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INFO);
-		CallIdHeader callIdHeader = sipLayer.getSipFactory().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId());
-		request = (SIPRequest)sipLayer.getSipFactory().createMessageFactory().createRequest(requestLine, Request.INFO, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
+		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(sipLayer.getSipFactory(), gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
 
-		Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
-		request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress));
+		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(sipLayer.getSipFactory(), gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
 
 		if (content != null) {
-			ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().createHeaderFactory().createContentTypeHeader("Application",
+			ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application",
 					"MANSRTSP");
 			request.setContent(content, contentTypeHeader);
 		}
@@ -289,56 +295,55 @@ public class SIPRequestHeaderProvider {
 
 		// via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(localIp, sipConfig.getPort(), sipResponse.getTopmostViaHeader().getTransport(), SipUtils.getNewViaTag());
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(localIp, sipConfig.getPort(), sipResponse.getTopmostViaHeader().getTransport(), SipUtils.getNewViaTag());
 		viaHeaders.add(viaHeader);
 
 		//Forwards
-		MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
 
 		//ceq
-		CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(sipResponse.getCSeqHeader().getSeqNumber(), Request.ACK);
+		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(sipResponse.getCSeqHeader().getSeqNumber(), Request.ACK);
 
-		Request request = sipLayer.getSipFactory().createMessageFactory().createRequest(sipURI, Request.ACK, sipResponse.getCallIdHeader(), cSeqHeader, sipResponse.getFromHeader(), sipResponse.getToHeader(), viaHeaders, maxForwards);
+		Request request = SipFactory.getInstance().createMessageFactory().createRequest(sipURI, Request.ACK, sipResponse.getCallIdHeader(), cSeqHeader, sipResponse.getFromHeader(), sipResponse.getToHeader(), viaHeaders, maxForwards);
 
-		request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
 
-		Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), localIp + ":"+sipConfig.getPort()));
-		request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress));
+		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(sipLayer.getSipFactory(), gitUtil));
+		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 = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress());
+		SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
 		// via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipConfig.getIp(), sipConfig.getPort(), device.getTransport(), viaTag);
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipConfig.getIp(), sipConfig.getPort(), device.getTransport(), viaTag);
 		viaHeader.setRPort();
 		viaHeaders.add(viaHeader);
 		// from
-		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
-		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, fromTag);
+		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 = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress());
-		Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress, toTag);
+		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 = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
 		// ceq
-		CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.MESSAGE);
+		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.MESSAGE);
 
-		ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
+		ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
 
-		request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader,
+		request = SipFactory.getInstance().createMessageFactory().createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader,
 				toHeader, viaHeaders, maxForwards, contentTypeHeader, content);
 
-		request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
 
 		return request;
 	}

+ 17 - 24
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java

@@ -1,6 +1,7 @@
 package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
 
 import com.alibaba.fastjson2.JSONObject;
+import com.genersoft.iot.vmp.common.InviteSessionType;
 import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.conf.SipConfig;
 import com.genersoft.iot.vmp.conf.UserSetting;
@@ -36,6 +37,7 @@ 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;
 import javax.sip.message.Request;
 import java.text.ParseException;
@@ -358,7 +360,7 @@ public class SIPCommander implements ISIPCommander {
             // 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值
             ResponseEvent responseEvent = (ResponseEvent) e.event;
             SIPResponse response = (SIPResponse) responseEvent.getResponse();
-            streamSession.put(device.getDeviceId(), channelId, "play", stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.play);
+            streamSession.put(device.getDeviceId(), channelId, "play", stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response, InviteSessionType.PLAY);
             okEvent.response(e);
         });
     }
@@ -373,11 +375,11 @@ public class SIPCommander implements ISIPCommander {
      */
     @Override
     public void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
-                                  String startTime, String endTime, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
+                                  String startTime, String endTime, ZlmHttpHookSubscribe.Event hookEvent,
                                   SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
 
 
-        logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getSdpIp(), mediaServerItem.getIp(), ssrcInfo.getPort());
+        logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getSdpIp(), ssrcInfo.getPort());
         String sdpIp;
         if (!ObjectUtils.isEmpty(device.getSdpIp())) {
             sdpIp = device.getSdpIp();
@@ -450,8 +452,7 @@ public class SIPCommander implements ISIPCommander {
         // 添加订阅
         subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json) -> {
             if (hookEvent != null) {
-                InviteStreamInfo inviteStreamInfo = new InviteStreamInfo(mediaServerItemInUse, json,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()).getCallId(), "rtp", ssrcInfo.getStream());
-                hookEvent.call(inviteStreamInfo);
+                hookEvent.response(mediaServerItemInUse, json);
             }
             subscribe.removeSubscribe(hookSubscribe);
         });
@@ -460,12 +461,9 @@ public class SIPCommander implements ISIPCommander {
         sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, event -> {
             ResponseEvent responseEvent = (ResponseEvent) event.event;
             SIPResponse response = (SIPResponse) responseEvent.getResponse();
-            streamSession.put(device.getDeviceId(), channelId,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()).getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.playback);
+            streamSession.put(device.getDeviceId(), channelId,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()).getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), response, InviteSessionType.PLAYBACK);
             okEvent.response(event);
         });
-        if (inviteStreamCallback != null) {
-            inviteStreamCallback.call(new InviteStreamInfo(mediaServerItem, null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()).getCallId(), "rtp", ssrcInfo.getStream()));
-        }
     }
 
     /**
@@ -480,10 +478,10 @@ public class SIPCommander implements ISIPCommander {
     @Override
     public void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
                                   String startTime, String endTime, int downloadSpeed,
-                                  InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
+                                  ZlmHttpHookSubscribe.Event hookEvent,
                                   SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
 
-        logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getSdpIp(), mediaServerItem.getIp(), ssrcInfo.getPort());
+        logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getSdpIp(), ssrcInfo.getPort());
         String sdpIp;
         if (!ObjectUtils.isEmpty(device.getSdpIp())) {
             sdpIp = device.getSdpIp();
@@ -551,13 +549,13 @@ public class SIPCommander implements ISIPCommander {
 
         content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc
         logger.debug("此时请求下载信令的ssrc===>{}",ssrcInfo.getSsrc());
-        HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, null, mediaServerItem.getId());
+        HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
         // 添加订阅
         CallIdHeader newCallIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()), device.getTransport());
-        String callId=newCallIdHeader.getCallId();
+        String callId= newCallIdHeader.getCallId();
         subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json) -> {
             logger.debug("sipc 添加订阅===callId {}",callId);
-            hookEvent.call(new InviteStreamInfo(mediaServerItem, json,callId, "rtp", ssrcInfo.getStream()));
+            hookEvent.response(mediaServerItemInUse, json);
             subscribe.removeSubscribe(hookSubscribe);
             hookSubscribe.getContent().put("regist", false);
             hookSubscribe.getContent().put("schema", "rtsp");
@@ -566,7 +564,7 @@ public class SIPCommander implements ISIPCommander {
                     (MediaServerItem mediaServerItemForEnd, JSONObject jsonForEnd) -> {
                         logger.info("[录像]下载结束, 发送BYE");
                         try {
-                            streamByeCmd(device, channelId, ssrcInfo.getStream(),callId);
+                            streamByeCmd(device, channelId, ssrcInfo.getStream(), callId);
                         } catch (InvalidArgumentException | ParseException | SipException |
                                  SsrcTransactionNotFoundException e) {
                             logger.error("[录像]下载结束, 发送BYE失败 {}", e.getMessage());
@@ -575,9 +573,6 @@ public class SIPCommander implements ISIPCommander {
         });
 
         Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, SipUtils.getNewFromTag(), null,newCallIdHeader, ssrcInfo.getSsrc());
-        if (inviteStreamCallback != null) {
-            inviteStreamCallback.call(new InviteStreamInfo(mediaServerItem, null,callId, "rtp", ssrcInfo.getStream()));
-        }
 
         sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, event -> {
             ResponseEvent responseEvent = (ResponseEvent) event.event;
@@ -588,9 +583,7 @@ public class SIPCommander implements ISIPCommander {
             if (ssrcIndex >= 0) {
                 ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
             }
-            logger.debug("接收到的下载响应ssrc====>{}",ssrcInfo.getSsrc());
-            logger.debug("接收到的下载响应ssrc====>{}",ssrc);
-            streamSession.put(device.getDeviceId(), channelId, response.getCallIdHeader().getCallId(), ssrcInfo.getStream(), ssrc, mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.download);
+            streamSession.put(device.getDeviceId(), channelId, response.getCallIdHeader().getCallId(), ssrcInfo.getStream(), ssrc, mediaServerItem.getId(), response, InviteSessionType.DOWNLOAD);
             okEvent.response(event);
         });
     }
@@ -654,7 +647,7 @@ public class SIPCommander implements ISIPCommander {
             // 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值
             ResponseEvent responseEvent = (ResponseEvent) e.event;
             SIPResponse response = (SIPResponse) responseEvent.getResponse();
-            streamSession.put(device.getDeviceId(), channelId, "talk", stream, sendRtpItem.getSsrc(), mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.talk);
+            streamSession.put(device.getDeviceId(), channelId, "talk", stream, sendRtpItem.getSsrc(), mediaServerItem.getId(), response, InviteSessionType.TALK);
             okEvent.response(e);
         });
     }
@@ -1247,7 +1240,7 @@ public class SIPCommander implements ISIPCommander {
         CallIdHeader callIdHeader;
 
         if (requestOld != null) {
-            callIdHeader = sipLayer.getSipFactory().createHeaderFactory().createCallIdHeader(requestOld.getCallIdHeader().getCallId());
+            callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(requestOld.getCallIdHeader().getCallId());
         } else {
             callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport());
         }
@@ -1322,7 +1315,7 @@ public class SIPCommander implements ISIPCommander {
         CallIdHeader callIdHeader;
 
         if (requestOld != null) {
-            callIdHeader = sipLayer.getSipFactory().createHeaderFactory().createCallIdHeader(requestOld.getCallIdHeader().getCallId());
+            callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(requestOld.getCallIdHeader().getCallId());
         } else {
             callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport());
         }

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

@@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
 
 import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONObject;
+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;
@@ -39,6 +40,7 @@ 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;
 import javax.sip.header.WWWAuthenticateHeader;
 import javax.sip.message.Request;
@@ -518,7 +520,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
     private void sendNotify(ParentPlatform parentPlatform, String catalogXmlContent,
                                    SubscribeInfo subscribeInfo, SipSubscribe.Event errorEvent,  SipSubscribe.Event okEvent )
             throws SipException, ParseException, InvalidArgumentException {
-        MessageFactoryImpl messageFactory = (MessageFactoryImpl) sipLayer.getSipFactory().createMessageFactory();
+        MessageFactoryImpl messageFactory = (MessageFactoryImpl) SipFactory.getInstance().createMessageFactory();
         String characterSet = parentPlatform.getCharacterSet();
         // 设置编码, 防止中文乱码
         messageFactory.setDefaultContentEncodingCharset(characterSet);
@@ -854,7 +856,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         }), e -> {
             ResponseEvent responseEvent = (ResponseEvent) e.event;
             SIPResponse response = (SIPResponse) responseEvent.getResponse();
-            streamSession.put(platform.getServerGBId(), channelId, callIdHeader.getCallId(),  stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.broadcast);
+            streamSession.put(platform.getServerGBId(), channelId, callIdHeader.getCallId(),  stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response, InviteSessionType.BROADCAST);
             okEvent.response(e);
         });
     }

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

@@ -1,9 +1,11 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
 
-import com.genersoft.iot.vmp.common.StreamInfo;
+import com.genersoft.iot.vmp.common.InviteInfo;
+import com.genersoft.iot.vmp.common.InviteSessionType;
 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;
@@ -13,6 +15,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorP
 import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.service.IDeviceService;
+import com.genersoft.iot.vmp.service.IInviteStreamService;
 import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.service.IPlayService;
 import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
@@ -29,6 +32,7 @@ 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;
@@ -56,6 +60,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
 	@Autowired
 	private IRedisCatchStorage redisCatchStorage;
 
+	@Autowired
+	private IInviteStreamService inviteStreamService;
+
 	@Autowired
 	private IDeviceService deviceService;
 
@@ -68,6 +75,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
 	@Autowired
 	private ZLMRTPServerFactory zlmrtpServerFactory;
 
+	@Autowired
+	private SSRCFactory ssrcFactory;
+
 	@Autowired
 	private IMediaServerService mediaServerService;
 
@@ -100,92 +110,89 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
 		} catch (SipException | InvalidArgumentException | ParseException e) {
 			logger.error("[回复BYE信息失败],{}", e.getMessage());
 		}
-
-		SendRtpItem sendRtpItem =  redisCatchStorage.querySendRTPServer(null, null, null, request.getCallIdHeader().getCallId());
-
-		if (sendRtpItem != null){
-			logger.info("[收到bye] {}/{}", sendRtpItem.getPlatformId(), sendRtpItem.getChannelId());
-			String streamId = sendRtpItem.getStream();
-			MediaServerItem mediaServerItem = mediaServerService.getOne(sendRtpItem.getMediaServerId());
-			if (mediaServerItem == null) {
-				return;
-			}
-
-			Boolean ready = zlmrtpServerFactory.isStreamReady(mediaServerItem, sendRtpItem.getApp(), streamId);
-			if (!ready) {
-				logger.info("[收到bye] 发现流{}/{}已经结束,不需处理", sendRtpItem.getApp(), sendRtpItem.getStream());
-				return;
-			}
-			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);
-			MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
-			redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(), request.getCallIdHeader().getCallId(), null);
-			zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param);
-
-			int totalReaderCount = zlmrtpServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId);
-			if (totalReaderCount <= 0) {
-				logger.info("[收到bye] {} 无其它观看者,通知设备停止推流", streamId);
-				if (sendRtpItem.getPlayType().equals(InviteStreamType.PLAY)) {
-					Device device = deviceService.getDevice(sendRtpItem.getDeviceId());
-					if (device == null) {
-						logger.info("[收到bye] {} 通知设备停止推流时未找到设备信息", streamId);
+		CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME);
+			String platformGbId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(FromHeader.NAME)).getAddress().getURI()).getUser();
+			String channelId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser();
+			SendRtpItem sendRtpItem =  redisCatchStorage.querySendRTPServer(platformGbId, channelId, null, callIdHeader.getCallId());
+			logger.info("[收到bye] {}/{}", platformGbId, channelId);
+			if (sendRtpItem != null){
+				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);
+				MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
+				redisCatchStorage.deleteSendRTPServer(platformGbId, channelId, callIdHeader.getCallId(), null);
+				ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc());
+				zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param);
+				int totalReaderCount = zlmrtpServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId);
+				if (totalReaderCount <= 0) {
+					logger.info("[收到bye] {} 无其它观看者,通知设备停止推流", streamId);
+					if (sendRtpItem.getPlayType().equals(InviteStreamType.PLAY)) {
+						Device device = deviceService.getDevice(sendRtpItem.getDeviceId());
+						if (device == null) {
+							logger.info("[收到bye] {} 通知设备停止推流时未找到设备信息", streamId);
+						}
+						try {
+							logger.warn("[停止点播] {}/{}", sendRtpItem.getDeviceId(), channelId);
+							cmder.streamByeCmd(device, channelId, streamId, null);
+						} catch (InvalidArgumentException | ParseException | SipException |
+								 SsrcTransactionNotFoundException e) {
+							logger.error("[收到bye] {} 无其它观看者,通知设备停止推流, 发送BYE失败 {}",streamId, e.getMessage());
+						}
 					}
-					try {
-						logger.warn("[停止点播] {}/{}", sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
-						cmder.streamByeCmd(device, sendRtpItem.getChannelId(), streamId, null);
-					} catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) {
-						logger.error("[收到bye] {} 无其它观看者,通知设备停止推流, 发送BYE失败 {}",streamId, e.getMessage());
+					if (sendRtpItem.getPlayType().equals(InviteStreamType.PUSH)) {
+						MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
+								sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(),
+								sendRtpItem.getPlatformId(), null, null, sendRtpItem.getMediaServerId());
+						redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel);
 					}
 				}
-
-				if (sendRtpItem.getPlayType().equals(InviteStreamType.PUSH)) {
-					MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
-							sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(),
-							sendRtpItem.getPlatformId(), null, null, sendRtpItem.getMediaServerId());
-					redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel);
-				}
 			}
+			// 可能是设备主动停止
+			Device device = storager.queryVideoDeviceByChannelId(platformGbId);
+			if (device != null) {
+				storager.stopPlay(device.getDeviceId(), channelId);
+				SsrcTransaction ssrcTransactionForPlay = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, "play", null);
+				if (ssrcTransactionForPlay != null){
+					if (ssrcTransactionForPlay.getCallId().equals(callIdHeader.getCallId())){
+						// 释放ssrc
+						MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransactionForPlay.getMediaServerId());
+						if (mediaServerItem != null) {
+							mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransactionForPlay.getSsrc());
+						}
+						streamSession.remove(device.getDeviceId(), channelId, ssrcTransactionForPlay.getStream());
+					}
+					InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId);
 
-			playService.stopAudioBroadcast(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
-		}
-
-		String platformGbId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(FromHeader.NAME)).getAddress().getURI()).getUser();
-		String channelId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser();
-
-		// 可能是设备主动停止
-		Device device = storager.queryVideoDeviceByChannelId(platformGbId);
-		if (device != null) {
-			storager.stopPlay(device.getDeviceId(), channelId);
-			StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(device.getDeviceId(), channelId);
-			if (streamInfo != null) {
-				redisCatchStorage.stopPlay(streamInfo);
-				mediaServerService.closeRTPServer(streamInfo.getMediaServerId(), streamInfo.getStream());
-			}
-			SsrcTransaction ssrcTransactionForPlay = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, "play", null);
-			if (ssrcTransactionForPlay != null){
-				if (ssrcTransactionForPlay.getCallId().equals(request.getCallIdHeader().getCallId())){
+					if (inviteInfo != null) {
+						inviteStreamService.removeInviteInfo(inviteInfo);
+						if (inviteInfo.getStreamInfo() != null) {
+							mediaServerService.closeRTPServer(inviteInfo.getStreamInfo().getMediaServerId(), inviteInfo.getStream());
+						}
+					}
+				}
+				SsrcTransaction ssrcTransactionForPlayBack = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, callIdHeader.getCallId(), null);
+				if (ssrcTransactionForPlayBack != null) {
 					// 释放ssrc
-					MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransactionForPlay.getMediaServerId());
+					MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransactionForPlayBack.getMediaServerId());
 					if (mediaServerItem != null) {
-						mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransactionForPlay.getSsrc());
+						mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransactionForPlayBack.getSsrc());
+					}
+					streamSession.remove(device.getDeviceId(), channelId, ssrcTransactionForPlayBack.getStream());
+					InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAYBACK, device.getDeviceId(), channelId);
+
+					if (inviteInfo != null) {
+						inviteStreamService.removeInviteInfo(inviteInfo);
+						if (inviteInfo.getStreamInfo() != null) {
+							mediaServerService.closeRTPServer(inviteInfo.getStreamInfo().getMediaServerId(), inviteInfo.getStream());
+						}
 					}
-					streamSession.remove(device.getDeviceId(), channelId, ssrcTransactionForPlay.getStream());
-				}
-			}
-			SsrcTransaction ssrcTransactionForPlayBack = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, request.getCallIdHeader().getCallId(), null);
-			if (ssrcTransactionForPlayBack != null) {
-				// 释放ssrc
-				MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransactionForPlayBack.getMediaServerId());
-				if (mediaServerItem != null) {
-					mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransactionForPlayBack.getSsrc());
 				}
-				streamSession.remove(device.getDeviceId(), channelId, ssrcTransactionForPlayBack.getStream());
 			}
-		}
+
 		SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(null, null, request.getCallIdHeader().getCallId(), null);
 		if (ssrcTransaction != null) {
 			// 释放ssrc
@@ -203,7 +210,7 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
 //						break;
 //					case download:
 //						break;
-				case broadcast:
+				case BROADCAST:
 					String channelId1 = ssrcTransaction.getChannelId();
 
 					Device deviceFromTransaction = storager.queryVideoDevice(ssrcTransaction.getDeviceId());
@@ -255,7 +262,7 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
 									List<SsrcTransaction> ssrcTransactions = streamSession.getSsrcTransactionForAll(null, channelId1, null, null);
 									if (ssrcTransactions.size() > 0) {
 										for (SsrcTransaction transaction : ssrcTransactions) {
-											if (transaction.getType().equals(VideoStreamSessionManager.SessionType.broadcast)) {
+											if (transaction.getType().equals(InviteSessionType.BROADCAST)) {
 												ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(transaction.getDeviceId());
 												if (parentPlatform != null) {
 													try {

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

@@ -1,15 +1,14 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
 
-import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.common.VideoManagerConstants;
+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.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.SsrcConfig;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
+import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
 import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
 import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
@@ -23,7 +22,12 @@ import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
 import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
 import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
 import com.genersoft.iot.vmp.media.zlm.dto.*;
-import com.genersoft.iot.vmp.service.*;
+import com.genersoft.iot.vmp.service.IMediaServerService;
+import com.genersoft.iot.vmp.service.IPlayService;
+import com.genersoft.iot.vmp.service.IStreamProxyService;
+import com.genersoft.iot.vmp.service.IStreamPushService;
+import com.genersoft.iot.vmp.service.bean.InviteErrorCallback;
+import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
 import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
 import com.genersoft.iot.vmp.service.bean.SSRCInfo;
 import com.genersoft.iot.vmp.service.redisMsg.RedisGbPlayMsgListener;
@@ -78,6 +82,9 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
     @Autowired
     private IRedisCatchStorage redisCatchStorage;
 
+    @Autowired
+    private SSRCFactory ssrcFactory;
+
     @Autowired
     private DynamicTask dynamicTask;
 
@@ -90,8 +97,8 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
     @Autowired
     private SIPSender sipSender;
 
-	@Autowired
-	private AudioBroadcastManager audioBroadcastManager;
+    @Autowired
+    private AudioBroadcastManager audioBroadcastManager;
 
     @Autowired
     private ZLMRTPServerFactory zlmrtpServerFactory;
@@ -102,8 +109,8 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
     @Autowired
     private ISIPCommander commander;
 
-	@Autowired
-	private ZLMRESTfulUtils zlmresTfulUtils;
+    @Autowired
+    private ZLMRESTfulUtils zlmresTfulUtils;
 
     @Autowired
     private ZlmHttpHookSubscribe zlmHttpHookSubscribe;
@@ -111,29 +118,25 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
     @Autowired
     private SIPProcessorObserver sipProcessorObserver;
 
-    @Autowired
-    private VideoStreamSessionManager sessionManager;
-
     @Autowired
     private UserSetting userSetting;
 
     @Autowired
     private ZLMMediaListManager mediaListManager;
 
-	@Autowired
-	private DeferredResultHolder resultHolder;
+    @Autowired
+    private DeferredResultHolder resultHolder;
 
-	@Autowired
-	private ZlmHttpHookSubscribe subscribe;
+    @Autowired
+    private ZlmHttpHookSubscribe subscribe;
 
-	@Autowired
-	private SipConfig config;
+    @Autowired
+    private SipConfig config;
 
     @Autowired
     private VideoStreamSessionManager streamSession;
 
 
-
     @Autowired
     private RedisGbPlayMsgListener redisGbPlayMsgListener;
 
@@ -153,7 +156,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
     public void process(RequestEvent evt) {
         //  Invite Request消息实现,此消息一般为级联消息,上级给下级发送请求视频指令
         try {
-            SIPRequest request = (SIPRequest)evt.getRequest();
+            SIPRequest request = (SIPRequest) evt.getRequest();
             String channelId = SipUtils.getChannelIdFromRequest(request);
             String requesterId = SipUtils.getUserIdFromFromHeader(request);
             CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME);
@@ -167,27 +170,6 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                 }
                 return;
             }
-            String ssrc = null;
-            SessionDescription sdp = null;
-            String ssrcDefault = "0000000000";
-            if (channelId == null) {
-                // 解析sdp消息, 使用jainsip 自带的sdp解析方式
-                String contentString = new String(request.getRawContent());
-
-                // jainSip不支持y=字段, 移除以解析。
-                int ssrcIndex = contentString.indexOf("y=");
-
-                if (ssrcIndex >= 0) {
-                    //ssrc规定长度为10个字节,不取余下长度以避免后续还有“f=”字段
-                    ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
-                    String substring = contentString.substring(0, contentString.indexOf("y="));
-                    sdp = SdpFactory.getInstance().createSessionDescription(substring);
-                } else {
-                    ssrc = ssrcDefault;
-                    sdp = SdpFactory.getInstance().createSessionDescription(contentString);
-                }
-                channelId = sdp.getOrigin().getUsername();
-            }
 
 
             // 查询请求是否来自上级平台\设备
@@ -249,7 +231,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);
@@ -285,23 +267,21 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                     }
                     return;
                 }
-                if (sdp == null || ssrc == null) {
-                    // 解析sdp消息, 使用jainsip 自带的sdp解析方式
-                    String contentString = new String(request.getRawContent());
-
-                    // jainSip不支持y=字段, 移除以解析。
-                    int ssrcIndex = contentString.indexOf("y=");
-                    if (ssrcIndex >= 0) {
-                        //ssrc规定长度为10个字节,不取余下长度以避免后续还有“f=”字段
-                        ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
-                        String substring = contentString.substring(0, contentString.indexOf("y="));
-                        sdp = SdpFactory.getInstance().createSessionDescription(substring);
-                    } else {
-                        ssrc = ssrcDefault;
-                        sdp = SdpFactory.getInstance().createSessionDescription(contentString);
-                    }
-                }
+                // 解析sdp消息, 使用jainsip 自带的sdp解析方式
+                String contentString = new String(request.getRawContent());
 
+                // jainSip不支持y=字段, 移除以解析。
+                // 检查是否有y字段
+                int ssrcIndex = contentString.indexOf("y=");
+
+                SessionDescription sdp;
+                if (ssrcIndex >= 0) {
+                    //ssrc规定长度为10个字节,不取余下长度以避免后续还有“f=”字段
+                    String substring = contentString.substring(0, ssrcIndex);
+                    sdp = SdpFactory.getInstance().createSessionDescription(substring);
+                } else {
+                    sdp = SdpFactory.getInstance().createSessionDescription(contentString);
+                }
                 String sessionName = sdp.getSessionName().getValue();
 
                 Long startTime = null;
@@ -363,7 +343,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                 String username = sdp.getOrigin().getUsername();
                 String addressStr = sdp.getConnection().getAddress();
 
-                logger.info("[上级点播]用户:{}, 通道:{}, 地址:{}:{}, ssrc:{}", username, channelId, addressStr, port, ssrc);
+
                 Device device = null;
                 // 通过 channel 和 gbStream 是否为null 值判断来源是直播流合适国标
                 if (channel != null) {
@@ -387,6 +367,25 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                         }
                         return;
                     }
+
+                    String ssrc;
+                    if (userSetting.getUseCustomSsrcForParentInvite() || ssrcIndex < 0) {
+                        // 上级平台点播时不使用上级平台指定的ssrc,使用自定义的ssrc,参考国标文档-点播外域设备媒体流SSRC处理方式
+                        ssrc = "Play".equalsIgnoreCase(sessionName) ? ssrcFactory.getPlaySsrc(mediaServerItem.getId()) : ssrcFactory.getPlayBackSsrc(mediaServerItem.getId());
+                    } else {
+                        ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
+                    }
+                    String streamTypeStr = null;
+                    if (mediaTransmissionTCP) {
+                        if (tcpActive) {
+                            streamTypeStr = "TCP-ACTIVE";
+                        } else {
+                            streamTypeStr = "TCP-PASSIVE";
+                        }
+                    } else {
+                        streamTypeStr = "UDP";
+                    }
+                    logger.info("[上级Invite] {}, 平台:{}, 通道:{}, 收流地址:{}:{},收流方式:{}, ssrc:{}", sessionName, username, channelId, addressStr, port, streamTypeStr, ssrc);
                     SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
                             device.getDeviceId(), channelId, mediaTransmissionTCP, platform.isRtcp());
 
@@ -407,11 +406,10 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
 
                     Long finalStartTime = startTime;
                     Long finalStopTime = stopTime;
-                    String finalChannelId = channelId;
-                    ZlmHttpHookSubscribe.Event hookEvent = (mediaServerItemInUSe, responseJSON) -> {
-                        String app = responseJSON.getString("app");
-                        String stream = responseJSON.getString("stream");
-                        logger.info("[上级点播]下级已经开始推流。 回复200OK(SDP), {}/{}", app, stream);
+                    InviteErrorCallback<Object> hookEvent = (code, msg, data) -> {
+                        StreamInfo streamInfo = (StreamInfo) data;
+                        MediaServerItem mediaServerItemInUSe = mediaServerService.getOne(streamInfo.getMediaServerId());
+                        logger.info("[上级Invite]下级已经开始推流。 回复200OK(SDP), {}/{}", streamInfo.getApp(), streamInfo.getStream());
                         //     * 0 等待设备推流上来
                         //     * 1 下级已经推流,等待上级平台回复ack
                         //     * 2 推流中
@@ -420,7 +418,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
 
                         StringBuffer content = new StringBuffer(200);
                         content.append("v=0\r\n");
-                        content.append("o=" + finalChannelId + " 0 0 IN IP4 " + mediaServerItemInUSe.getSdpIp() + "\r\n");
+                        content.append("o=" + channelId + " 0 0 IN IP4 " + mediaServerItemInUSe.getSdpIp() + "\r\n");
                         content.append("s=" + sessionName + "\r\n");
                         content.append("c=IN IP4 " + mediaServerItemInUSe.getSdpIp() + "\r\n");
                         if ("Playback".equalsIgnoreCase(sessionName)) {
@@ -462,111 +460,118 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                             logger.error("[命令发送失败] 国标级联 回复SdpAck", e);
                         }
                     };
-                    SipSubscribe.Event errorEvent = ((event) -> {
+                    InviteErrorCallback<Object> errorEvent = ((statusCode, msg, data) -> {
                         // 未知错误。直接转发设备点播的错误
                         try {
-                            Response response = getMessageFactory().createResponse(event.statusCode, evt.getRequest());
-                            sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
-                        } catch (ParseException | SipException  e) {
+                            if (statusCode > 0) {
+                                Response response = getMessageFactory().createResponse(statusCode, evt.getRequest());
+                                sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
+                            }
+                        } catch (ParseException | SipException e) {
                             logger.error("未处理的异常 ", e);
                         }
                     });
                     sendRtpItem.setApp("rtp");
                     if ("Playback".equalsIgnoreCase(sessionName)) {
                         sendRtpItem.setPlayType(InviteStreamType.PLAYBACK);
-                        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, null, device.isSsrcCheck(), true);
+                        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, null, null, device.isSsrcCheck(), true, 0, false, false, device.getStreamModeForParam());
                         sendRtpItem.setStream(ssrcInfo.getStream());
                         // 写入redis, 超时时回复
                         redisCatchStorage.updateSendRTPSever(sendRtpItem);
                         playService.playBack(mediaServerItem, ssrcInfo, device.getDeviceId(), channelId, DateUtil.formatter.format(start),
-                                DateUtil.formatter.format(end), null, result -> {
-                                    if (result.getCode() != 0) {
-                                        logger.warn("录像回放失败");
-                                        if (result.getEvent() != null) {
-                                            errorEvent.response(result.getEvent());
-                                        }
-                                        redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), finalChannelId, callIdHeader.getCallId(), null);
-                                        try {
-                                            responseAck(request, Response.REQUEST_TIMEOUT);
-                                        } catch (SipException | InvalidArgumentException | ParseException e) {
-                                            logger.error("[命令发送失败] 国标级联 录像回放 发送REQUEST_TIMEOUT: {}", e.getMessage());
-                                        }
+                                DateUtil.formatter.format(end),
+                                (code, msg, data) -> {
+                                    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()) {
+                                        logger.info("[录像回放]超时, 用户:{}, 通道:{}", username, channelId);
+                                        redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), channelId, callIdHeader.getCallId(), null);
+                                        errorEvent.run(code, msg, data);
+                                    } else {
+                                        errorEvent.run(code, msg, data);
+                                    }
+                                });
+                    } else if ("Download".equalsIgnoreCase(sessionName)) {
+                        // 获取指定的下载速度
+                        Vector sdpMediaDescriptions = sdp.getMediaDescriptions(true);
+                        MediaDescription mediaDescription = null;
+                        String downloadSpeed = "1";
+                        if (sdpMediaDescriptions.size() > 0) {
+                            mediaDescription = (MediaDescription) sdpMediaDescriptions.get(0);
+                        }
+                        if (mediaDescription != null) {
+                            downloadSpeed = mediaDescription.getAttribute("downloadspeed");
+                        }
+
+                        sendRtpItem.setPlayType(InviteStreamType.DOWNLOAD);
+                        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),
+                                DateUtil.formatter.format(end), Integer.parseInt(downloadSpeed),
+                                (code, msg, data) -> {
+                                    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()) {
+                                        logger.info("[录像下载]超时, 用户:{}, 通道:{}", username, channelId);
+                                        redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), channelId, callIdHeader.getCallId(), null);
+                                        errorEvent.run(code, msg, data);
                                     } else {
-                                        if (result.getMediaServerItem() != null) {
-                                            hookEvent.response(result.getMediaServerItem(), result.getResponse());
-                                        }
+                                        errorEvent.run(code, msg, data);
                                     }
                                 });
                     } else {
                         sendRtpItem.setPlayType(InviteStreamType.PLAY);
-                        SsrcTransaction playTransaction = sessionManager.getSsrcTransaction(device.getDeviceId(), channelId, "play", null);
-                        if (playTransaction != null) {
-                            Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, "rtp", playTransaction.getStream());
-                            if (!streamReady) {
-                                boolean hasRtpServer = mediaServerService.checkRtpServer(mediaServerItem, "rtp", playTransaction.getStream());
-                                if (hasRtpServer) {
-                                    logger.info("[上级点播]已经开启rtpServer但是尚未收到流,开启监听流的到来");
-                                    HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", playTransaction.getStream(), true, "rtsp", mediaServerItem.getId());
-                                    zlmHttpHookSubscribe.addSubscribe(hookSubscribe, hookEvent);
-                                }else {
-                                    playTransaction = null;
-                                }
-                            }
-                        }
-                        if (playTransaction == null) {
-                            String streamId = null;
-                            if (mediaServerItem.isRtpEnable()) {
-                                streamId = String.format("%s_%s", device.getDeviceId(), channelId);
-                            }
-                            SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, null, device.isSsrcCheck(), false);
-                            logger.info(JSONObject.toJSONString(ssrcInfo));
-                            sendRtpItem.setStream(ssrcInfo.getStream());
-                            sendRtpItem.setSsrc(ssrc.equals(ssrcDefault) ? ssrcInfo.getSsrc() : ssrc);
-
-                            // 写入redis, 超时时回复
-                            redisCatchStorage.updateSendRTPSever(sendRtpItem);
-                            playService.play(mediaServerItem, ssrcInfo, device, channelId, hookEvent, errorEvent, (code, msg) -> {
-                                logger.info("[上级点播]超时, 用户:{}, 通道:{}", username, finalChannelId);
-                                redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), finalChannelId, callIdHeader.getCallId(), null);
-                            });
+                        String streamId = null;
+                        if (mediaServerItem.isRtpEnable()) {
+                            streamId = String.format("%s_%s", device.getDeviceId(), channelId);
                         } else {
-                            sendRtpItem.setStream(playTransaction.getStream());
-                            // 写入redis, 超时时回复
-                            redisCatchStorage.updateSendRTPSever(sendRtpItem);
-                            JSONObject jsonObject = new JSONObject();
-                            jsonObject.put("app", sendRtpItem.getApp());
-                            jsonObject.put("stream", sendRtpItem.getStream());
-                            hookEvent.response(mediaServerItem, jsonObject);
+                            streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
                         }
+                        sendRtpItem.setStream(streamId);
+                        redisCatchStorage.updateSendRTPSever(sendRtpItem);
+                        playService.play(mediaServerItem, device.getDeviceId(), channelId, ((code, msg, data) -> {
+                            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()) {
+                                logger.info("[上级点播]超时, 用户:{}, 通道:{}", username, channelId);
+                                redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), channelId, callIdHeader.getCallId(), null);
+                                errorEvent.run(code, msg, data);
+                            } else {
+                                errorEvent.run(code, msg, data);
+                            }
+                        }));
+
                     }
                 } else if (gbStream != null) {
-                    if(ssrc.equals(ssrcDefault))
-                    {
-                        SsrcConfig ssrcConfig = mediaServerItem.getSsrcConfig();
-                        if(ssrcConfig != null)
-                        {
-                            ssrc = ssrcConfig.getPlaySsrc();
-                            ssrcConfig.releaseSsrc(ssrc);
-                        }
+
+                    String ssrc;
+                    if (userSetting.getUseCustomSsrcForParentInvite() || ssrcIndex < 0) {
+                        // 上级平台点播时不使用上级平台指定的ssrc,使用自定义的ssrc,参考国标文档-点播外域设备媒体流SSRC处理方式
+                        ssrc = "Play".equalsIgnoreCase(sessionName) ? ssrcFactory.getPlaySsrc(mediaServerItem.getId()) : ssrcFactory.getPlayBackSsrc(mediaServerItem.getId());
+                    } else {
+                        ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
                     }
-                    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);
                             }
                         }
@@ -586,42 +591,43 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
      * 安排推流
      */
     private void pushProxyStream(RequestEvent evt, SIPRequest request, GbStream gbStream, ParentPlatform platform,
-                            CallIdHeader callIdHeader, MediaServerItem mediaServerItem,
-                            int port, Boolean tcpActive, boolean mediaTransmissionTCP,
-                            String channelId, String addressStr, String ssrc, String requesterId) {
-            Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, gbStream.getApp(), gbStream.getStream());
-            if (streamReady) {
-                // 自平台内容
-                SendRtpItem sendRtpItem = zlmrtpServerFactory.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());
+                                 CallIdHeader callIdHeader, MediaServerItem mediaServerItem,
+                                 int port, Boolean tcpActive, boolean mediaTransmissionTCP,
+                                 String channelId, String addressStr, String ssrc, String requesterId) {
+        Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, gbStream.getApp(), gbStream.getStream());
+        if (streamReady != null && streamReady) {
+            // 自平台内容
+            SendRtpItem sendRtpItem = zlmrtpServerFactory.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());
                 }
-                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,
@@ -629,7 +635,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
         // 推流
         if (streamPushItem.isSelf()) {
             Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, gbStream.getApp(), gbStream.getStream());
-            if (streamReady) {
+            if (streamReady != null && streamReady) {
                 // 自平台内容
                 SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
                         gbStream.getApp(), gbStream.getStream(), channelId, mediaTransmissionTCP, platform.isRtcp());
@@ -661,7 +667,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
 
             } 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);
             }
 
@@ -671,6 +677,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                     mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
         }
     }
+
     /**
      * 通知流上线
      */
@@ -688,7 +695,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                 String stream = responseJSON.getString("stream");
                 logger.info("[上级点播]拉流代理已经就绪, {}/{}", app, stream);
                 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(), () -> {
@@ -707,7 +714,6 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
             }
 
 
-
         } else if ("push".equals(gbStream.getStreamType())) {
             if (!platform.isStartOfflinePush()) {
                 // 平台设置中关闭了拉起离线的推流则直接回复
@@ -811,7 +817,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
         // 发送redis消息
         redisGbPlayMsgListener.sendMsg(streamPushItem.getServerId(), streamPushItem.getMediaServerId(),
                 streamPushItem.getApp(), streamPushItem.getStream(), addressStr, port, ssrc, requesterId,
-                channelId, mediaTransmissionTCP, platform.isRtcp(),null, responseSendItemMsg -> {
+                channelId, mediaTransmissionTCP, platform.isRtcp(), null, responseSendItemMsg -> {
                     SendRtpItem sendRtpItem = responseSendItemMsg.getSendRtpItem();
                     if (sendRtpItem == null || responseSendItemMsg.getMediaServerItem() == null) {
                         logger.warn("服务器端口资源不足");
@@ -836,7 +842,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());
                     }
@@ -877,8 +883,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");
@@ -953,7 +958,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
         }
         if (device != null) {
             logger.info("收到设备" + requesterId + "的语音广播Invite请求");
-            String key = VideoManagerConstants.BROADCAST_WAITE_INVITE +  device.getDeviceId() + broadcastCatch.getChannelId();
+            String key = VideoManagerConstants.BROADCAST_WAITE_INVITE + device.getDeviceId() + broadcastCatch.getChannelId();
             dynamicTask.stop(key);
             try {
                 responseAck(request, Response.TRYING);
@@ -986,7 +991,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                 boolean mediaTransmissionTCP = false;
                 Boolean tcpActive = null;
                 for (int i = 0; i < mediaDescriptions.size(); i++) {
-                    MediaDescription mediaDescription = (MediaDescription)mediaDescriptions.get(i);
+                    MediaDescription mediaDescription = (MediaDescription) mediaDescriptions.get(i);
                     Media media = mediaDescription.getMedia();
 
                     Vector mediaFormats = media.getMediaFormats(false);
@@ -1022,7 +1027,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                 }
                 String addressStr = sdp.getOrigin().getAddress();
                 logger.info("设备{}请求语音流,地址:{}:{},ssrc:{}, {}", requesterId, addressStr, port, ssrc,
-                        mediaTransmissionTCP ? (tcpActive? "TCP主动":"TCP被动") : "UDP");
+                        mediaTransmissionTCP ? (tcpActive ? "TCP主动" : "TCP被动") : "UDP");
 
                 MediaServerItem mediaServerItem = broadcastCatch.getMediaServerItem();
                 if (mediaServerItem == null) {
@@ -1036,7 +1041,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                     return;
                 }
                 logger.info("设备{}请求语音流, 收流地址:{}:{},ssrc:{}, {}, 对讲方式:{}", requesterId, addressStr, port, ssrc,
-                        mediaTransmissionTCP ? (tcpActive? "TCP主动":"TCP被动") : "UDP", sdp.getSessionName().getValue());
+                        mediaTransmissionTCP ? (tcpActive ? "TCP主动" : "TCP被动") : "UDP", sdp.getSessionName().getValue());
 
                 SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
                         device.getDeviceId(), broadcastCatch.getChannelId(),
@@ -1076,7 +1081,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                 Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, broadcastCatch.getApp(), broadcastCatch.getStream());
                 if (streamReady) {
                     sendOk(device, sendRtpItem, sdp, request, mediaServerItem, mediaTransmissionTCP, ssrc);
-                }else {
+                } else {
                     logger.warn("[语音通话], 未发现待推送的流,app={},stream={}", broadcastCatch.getApp(), broadcastCatch.getStream());
                     try {
                         responseAck(request, Response.GONE);
@@ -1093,29 +1098,30 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
         } 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 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("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("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("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");
@@ -1125,11 +1131,11 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                 content.append("a=connection:new\r\n");
                 if (!sendRtpItem.isTcpActive()) {
                     content.append("a=setup:active\r\n");
-                }else {
+                } else {
                     content.append("a=setup:passive\r\n");
                 }
             }
-            content.append("y="+ ssrc + "\r\n");
+            content.append("y=" + ssrc + "\r\n");
             content.append("f=v/////a/1/8/1\r\n");
 
             ParentPlatform parentPlatform = new ParentPlatform();

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

@@ -0,0 +1,281 @@
+package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
+
+import com.genersoft.iot.vmp.conf.DynamicTask;
+import com.genersoft.iot.vmp.conf.UserSetting;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
+import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
+import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
+import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
+import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
+import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
+import com.genersoft.iot.vmp.service.IDeviceChannelService;
+import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
+import org.dom4j.DocumentException;
+import org.dom4j.Element;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.sip.RequestEvent;
+import javax.sip.header.FromHeader;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * SIP命令类型: NOTIFY请求中的目录请求处理
+ */
+@Component
+public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent {
+
+
+    private final static Logger logger = LoggerFactory.getLogger(NotifyRequestForCatalogProcessor.class);
+
+	private final List<DeviceChannel> updateChannelOnlineList = new CopyOnWriteArrayList<>();
+	private final List<DeviceChannel> updateChannelOfflineList = new CopyOnWriteArrayList<>();
+	private final Map<String, DeviceChannel> updateChannelMap = new ConcurrentHashMap<>();
+
+	private final Map<String, DeviceChannel> addChannelMap = new ConcurrentHashMap<>();
+	private final List<DeviceChannel> deleteChannelList = new CopyOnWriteArrayList<>();
+
+
+	@Autowired
+	private UserSetting userSetting;
+
+	@Autowired
+	private EventPublisher eventPublisher;
+
+	@Autowired
+	private IRedisCatchStorage redisCatchStorage;
+
+	@Autowired
+	private IDeviceChannelService deviceChannelService;
+
+	@Autowired
+	private DynamicTask dynamicTask;
+
+	private final static String talkKey = "notify-request-for-catalog-task";
+
+	public void process(RequestEvent evt) {
+		try {
+			long start = System.currentTimeMillis();
+			FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME);
+			String deviceId = SipUtils.getUserIdFromFromHeader(fromHeader);
+
+			Device device = redisCatchStorage.getDevice(deviceId);
+			if (device == null || device.getOnline() == 0) {
+				logger.warn("[收到目录订阅]:{}, 但是设备已经离线", (device != null ? device.getDeviceId():"" ));
+				return;
+			}
+			Element rootElement = getRootElement(evt, device.getCharset());
+			if (rootElement == null) {
+				logger.warn("[ 收到目录订阅 ] content cannot be null, {}", evt.getRequest());
+				return;
+			}
+			Element deviceListElement = rootElement.element("DeviceList");
+			if (deviceListElement == null) {
+				return;
+			}
+			Iterator<Element> deviceListIterator = deviceListElement.elementIterator();
+			if (deviceListIterator != null) {
+
+				// 遍历DeviceList
+				while (deviceListIterator.hasNext()) {
+					Element itemDevice = deviceListIterator.next();
+					Element channelDeviceElement = itemDevice.element("DeviceID");
+					if (channelDeviceElement == null) {
+						continue;
+					}
+					Element eventElement = itemDevice.element("Event");
+					String event;
+					if (eventElement == null) {
+						logger.warn("[收到目录订阅]:{}, 但是Event为空, 设为默认值 ADD", (device != null ? device.getDeviceId():"" ));
+						event = CatalogEvent.ADD;
+					}else {
+						event = eventElement.getText().toUpperCase();
+					}
+					DeviceChannel channel = XmlUtil.channelContentHander(itemDevice, device, event);
+
+					channel.setDeviceId(device.getDeviceId());
+					logger.info("[收到目录订阅]:{}/{}", device.getDeviceId(), channel.getChannelId());
+					switch (event) {
+						case CatalogEvent.ON:
+							// 上线
+							logger.info("[收到通道上线通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
+							updateChannelOnlineList.add(channel);
+							if (updateChannelOnlineList.size() > 300) {
+								executeSaveForOnline();
+							}
+							if (userSetting.getDeviceStatusNotify()) {
+								// 发送redis消息
+								redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), channel.getChannelId(), true);
+							}
+
+							break;
+						case CatalogEvent.OFF :
+							// 离线
+							logger.info("[收到通道离线通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
+							if (userSetting.getRefuseChannelStatusChannelFormNotify()) {
+								logger.info("[收到通道离线通知] 但是平台已配置拒绝此消息,来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
+							}else {
+								updateChannelOfflineList.add(channel);
+								if (updateChannelOfflineList.size() > 300) {
+									executeSaveForOffline();
+								}
+								if (userSetting.getDeviceStatusNotify()) {
+									// 发送redis消息
+									redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), channel.getChannelId(), false);
+								}
+							}
+							break;
+						case CatalogEvent.VLOST:
+							// 视频丢失
+							logger.info("[收到通道视频丢失通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
+							if (userSetting.getRefuseChannelStatusChannelFormNotify()) {
+								logger.info("[收到通道视频丢失通知] 但是平台已配置拒绝此消息,来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
+							}else {
+								updateChannelOfflineList.add(channel);
+								if (updateChannelOfflineList.size() > 300) {
+									executeSaveForOffline();
+								}
+								if (userSetting.getDeviceStatusNotify()) {
+									// 发送redis消息
+									redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), channel.getChannelId(), false);
+								}
+							}
+							break;
+						case CatalogEvent.DEFECT:
+							// 故障
+							logger.info("[收到通道视频故障通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
+							if (userSetting.getRefuseChannelStatusChannelFormNotify()) {
+								logger.info("[收到通道视频故障通知] 但是平台已配置拒绝此消息,来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
+							}else {
+								updateChannelOfflineList.add(channel);
+								if (updateChannelOfflineList.size() > 300) {
+									executeSaveForOffline();
+								}
+								if (userSetting.getDeviceStatusNotify()) {
+									// 发送redis消息
+									redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), channel.getChannelId(), false);
+								}
+							}
+							break;
+						case CatalogEvent.ADD:
+							// 增加
+							logger.info("[收到增加通道通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
+							// 判断此通道是否存在
+							DeviceChannel deviceChannel = deviceChannelService.getOne(deviceId, channel.getChannelId());
+							if (deviceChannel != null) {
+								channel.setId(deviceChannel.getId());
+								updateChannelMap.put(channel.getChannelId(), channel);
+								if (updateChannelMap.keySet().size() > 300) {
+									executeSaveForUpdate();
+								}
+							}else {
+								addChannelMap.put(channel.getChannelId(), channel);
+								if (addChannelMap.keySet().size() > 300) {
+									executeSaveForAdd();
+								}
+							}
+
+							break;
+						case CatalogEvent.DEL:
+							// 删除
+							logger.info("[收到删除通道通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
+							deleteChannelList.add(channel);
+							if (deleteChannelList.size() > 300) {
+								executeSaveForDelete();
+							}
+							break;
+						case CatalogEvent.UPDATE:
+							// 更新
+							logger.info("[收到更新通道通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
+							// 判断此通道是否存在
+							DeviceChannel deviceChannelForUpdate = deviceChannelService.getOne(deviceId, channel.getChannelId());
+							if (deviceChannelForUpdate != null) {
+								channel.setId(deviceChannelForUpdate.getId());
+								updateChannelMap.put(channel.getChannelId(), channel);
+								if (updateChannelMap.keySet().size() > 300) {
+									executeSaveForUpdate();
+								}
+							}else {
+								addChannelMap.put(channel.getChannelId(), channel);
+								if (addChannelMap.keySet().size() > 300) {
+									executeSaveForAdd();
+								}
+							}
+							break;
+						default:
+							logger.warn("[ NotifyCatalog ] event not found : {}", event );
+
+					}
+					// 转发变化信息
+					eventPublisher.catalogEventPublish(null, channel, event);
+
+					if (updateChannelMap.keySet().size() > 0
+							|| addChannelMap.keySet().size() > 0
+							|| updateChannelOnlineList.size() > 0
+							|| updateChannelOfflineList.size() > 0
+							|| deleteChannelList.size() > 0) {
+
+						if (!dynamicTask.contains(talkKey)) {
+							dynamicTask.startDelay(talkKey, this::executeSave, 1000);
+						}
+					}
+				}
+			}
+		} catch (DocumentException e) {
+			logger.error("未处理的异常 ", e);
+		}
+	}
+
+	private void executeSave(){
+		System.out.println("定时存储数据");
+		executeSaveForUpdate();
+		executeSaveForDelete();
+		executeSaveForOnline();
+		executeSaveForOffline();
+		dynamicTask.stop(talkKey);
+	}
+
+	private void executeSaveForUpdate(){
+		if (updateChannelMap.values().size() > 0) {
+			ArrayList<DeviceChannel> deviceChannels = new ArrayList<>(updateChannelMap.values());
+			updateChannelMap.clear();
+			deviceChannelService.batchUpdateChannel(deviceChannels);
+		}
+
+	}
+
+	private void executeSaveForAdd(){
+		if (addChannelMap.values().size() > 0) {
+			ArrayList<DeviceChannel> deviceChannels = new ArrayList<>(addChannelMap.values());
+			addChannelMap.clear();
+			deviceChannelService.batchAddChannel(deviceChannels);
+		}
+	}
+
+	private void executeSaveForDelete(){
+		if (deleteChannelList.size() > 0) {
+			deviceChannelService.deleteChannels(deleteChannelList);
+			deleteChannelList.clear();
+		}
+	}
+
+	private void executeSaveForOnline(){
+		if (updateChannelOnlineList.size() > 0) {
+			deviceChannelService.channelsOnline(updateChannelOnlineList);
+			updateChannelOnlineList.clear();
+		}
+	}
+
+	private void executeSaveForOffline(){
+		if (updateChannelOfflineList.size() > 0) {
+			deviceChannelService.channelsOffline(updateChannelOfflineList);
+			updateChannelOfflineList.clear();
+		}
+	}
+
+}

+ 45 - 31
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestProcessor.java

@@ -76,12 +76,17 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 	@Autowired
 	private IDeviceChannelService deviceChannelService;
 
+	@Autowired
+	private NotifyRequestForCatalogProcessor notifyRequestForCatalogProcessor;
+
 	private ConcurrentLinkedQueue<HandlerCatchData> taskQueue = new ConcurrentLinkedQueue<>();
 
 	@Qualifier("taskExecutor")
 	@Autowired
 	private ThreadPoolTaskExecutor taskExecutor;
 
+	private int maxQueueCount = 30000;
+
 	@Override
 	public void afterPropertiesSet() throws Exception {
 		// 添加消息处理的订阅
@@ -91,43 +96,52 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 	@Override
 	public void process(RequestEvent evt) {
 		try {
-			responseAck((SIPRequest) evt.getRequest(), Response.OK, null, null);
+
+			if (taskQueue.size() >= userSetting.getMaxNotifyCountQueue()) {
+				responseAck((SIPRequest) evt.getRequest(), Response.BUSY_HERE, null, null);
+				logger.error("[notify] 待处理消息队列已满 {},返回486 BUSY_HERE,消息不做处理", userSetting.getMaxNotifyCountQueue());
+				return;
+			}else {
+				responseAck((SIPRequest) evt.getRequest(), Response.OK, null, null);
+			}
+
 		}catch (SipException | InvalidArgumentException | ParseException e) {
 			logger.error("未处理的异常 ", e);
 		}
 		boolean runed = !taskQueue.isEmpty();
+		logger.info("[notify] 待处理消息数量: {}", taskQueue.size());
 		taskQueue.offer(new HandlerCatchData(evt, null, null));
 		if (!runed) {
 			taskExecutor.execute(()-> {
-				try {
-					while (!taskQueue.isEmpty()) {
-						try {
-							HandlerCatchData take = taskQueue.poll();
-							Element rootElement = getRootElement(take.getEvt());
-							if (rootElement == null) {
-								logger.error("处理NOTIFY消息时未获取到消息体,{}", take.getEvt().getRequest());
-								continue;
-							}
-							String cmd = XmlUtil.getText(rootElement, "CmdType");
-
-							if (CmdType.CATALOG.equals(cmd)) {
-								logger.info("接收到Catalog通知");
-								processNotifyCatalogList(take.getEvt());
-							} else if (CmdType.ALARM.equals(cmd)) {
-								logger.info("接收到Alarm通知");
-								processNotifyAlarm(take.getEvt());
-							} else if (CmdType.MOBILE_POSITION.equals(cmd)) {
-								logger.info("接收到MobilePosition通知");
-								processNotifyMobilePosition(take.getEvt());
-							} else {
-								logger.info("接收到消息:" + cmd);
-							}
-						} catch (DocumentException e) {
-							logger.error("处理NOTIFY消息时错误", e);
+				while (!taskQueue.isEmpty()) {
+					try {
+						HandlerCatchData take = taskQueue.poll();
+						if (take == null) {
+							continue;
+						}
+						Element rootElement = getRootElement(take.getEvt());
+						if (rootElement == null) {
+							logger.error("处理NOTIFY消息时未获取到消息体,{}", take.getEvt().getRequest());
+							continue;
+						}
+						String cmd = XmlUtil.getText(rootElement, "CmdType");
+
+						if (CmdType.CATALOG.equals(cmd)) {
+							logger.info("接收到Catalog通知");
+//							processNotifyCatalogList(take.getEvt());
+							notifyRequestForCatalogProcessor.process(take.getEvt());
+						} else if (CmdType.ALARM.equals(cmd)) {
+							logger.info("接收到Alarm通知");
+							processNotifyAlarm(take.getEvt());
+						} else if (CmdType.MOBILE_POSITION.equals(cmd)) {
+							logger.info("接收到MobilePosition通知");
+							processNotifyMobilePosition(take.getEvt());
+						} else {
+							logger.info("接收到消息:" + cmd);
 						}
+					} catch (DocumentException e) {
+						logger.error("处理NOTIFY消息时错误", e);
 					}
-				}catch (Exception e) {
-					logger.error("处理NOTIFY消息时错误", e);
 				}
 			});
 		}
@@ -135,7 +149,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 
 	/**
 	 * 处理MobilePosition移动位置Notify
-	 * 
+	 *
 	 * @param evt
 	 */
 	private void processNotifyMobilePosition(RequestEvent evt) {
@@ -239,7 +253,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 
 	/***
 	 * 处理alarm设备报警Notify
-	 * 
+	 *
 	 * @param evt
 	 */
 	private void processNotifyAlarm(RequestEvent evt) {
@@ -349,7 +363,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 
 	/***
 	 * 处理catalog设备目录列表Notify
-	 * 
+	 *
 	 * @param evt
 	 */
 	private void processNotifyCatalogList(RequestEvent evt) {

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

@@ -83,21 +83,7 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
     public void process(RequestEvent evt) {
         try {
             RequestEventExt evtExt = (RequestEventExt) evt;
-            String requestAddress = evtExt.getRemoteIpAddress() + ":" + evtExt.getRemotePort();
-
-//            MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
-//            QueryExp protocol = Query.match(Query.attr("protocol"), Query.value("HTTP/1.1"));
-////            ObjectName name = new ObjectName("*:type=Connector,*");
-//            ObjectName name = new ObjectName("*:*");
-//            Set<ObjectName> objectNames = beanServer.queryNames(name, protocol);
-//            for (ObjectName objectName : objectNames) {
-//                String catalina = objectName.getDomain();
-//                if ("Catalina".equals(catalina)) {
-//                    System.out.println(objectName.getKeyProperty("port"));
-//                }
-//            }
-
-//            System.out.println(ServiceInfo.getServerPort());
+
             SIPRequest request = (SIPRequest)evt.getRequest();
             Response response = null;
             boolean passwordCorrect = false;
@@ -107,12 +93,13 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
             AddressImpl address = (AddressImpl) fromHeader.getAddress();
             SipUri uri = (SipUri) address.getURI();
             String deviceId = uri.getUser();
-            logger.info("[注册请求] 设备:{}, 开始处理: {}", deviceId, requestAddress);
+
             Device device = deviceService.getDevice(deviceId);
 
             RemoteAddressInfo remoteAddressInfo = SipUtils.getRemoteAddressFromRequest(request,
                     userSetting.getSipUseSourceIpAsRemoteAddress());
-
+            String requestAddress = remoteAddressInfo.getIp() + ":" + remoteAddressInfo.getPort();
+                    logger.info("[注册请求] 设备:{}, 开始处理: {}", deviceId, requestAddress);
             if (device != null &&
                 device.getSipTransactionInfo() != null &&
                 request.getCallIdHeader().getCallId().equals(device.getSipTransactionInfo().getCallId())) {

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

@@ -1,6 +1,7 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.info;
 
-import com.genersoft.iot.vmp.common.StreamInfo;
+import com.genersoft.iot.vmp.common.InviteInfo;
+import com.genersoft.iot.vmp.common.InviteSessionType;
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
@@ -9,6 +10,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
 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;
+import com.genersoft.iot.vmp.service.IInviteStreamService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import gov.nist.javax.sip.message.SIPRequest;
@@ -17,10 +19,12 @@ 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.header.*;
+import javax.sip.header.CallIdHeader;
+import javax.sip.header.ContentTypeHeader;
 import javax.sip.message.Response;
 import java.text.ParseException;
 
@@ -43,6 +47,9 @@ public class InfoRequestProcessor extends SIPRequestProcessorParent implements I
     @Autowired
     private IRedisCatchStorage redisCatchStorage;
 
+    @Autowired
+    private IInviteStreamService inviteStreamService;
+
     @Autowired
     private IVideoManagerStorage storager;
 
@@ -103,27 +110,30 @@ public class InfoRequestProcessor extends SIPRequestProcessorParent implements I
                 if ("Application".equalsIgnoreCase(contentType) && "MANSRTSP".equalsIgnoreCase(contentSubType)) {
                     SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, null, callIdHeader.getCallId());
                     String streamId = sendRtpItem.getStream();
-                    StreamInfo streamInfo = redisCatchStorage.queryPlayback(null, null, streamId, null);
-                    if (null == streamInfo) {
+                    InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(InviteSessionType.PLAYBACK, streamId);
+                    if (null == inviteInfo) {
                         responseAck(request, Response.NOT_FOUND, "stream " + streamId + " not found");
                         return;
                     }
-                    Device device1 = storager.queryVideoDevice(streamInfo.getDeviceID());
-                    cmder.playbackControlCmd(device1,streamInfo,new String(evt.getRequest().getRawContent()),eventResult -> {
-                        // 失败的回复
-                        try {
-                            responseAck(request, eventResult.statusCode, eventResult.msg);
-                        } catch (SipException | InvalidArgumentException | ParseException e) {
-                            logger.error("[命令发送失败] 国标级联 录像控制: {}", e.getMessage());
-                        }
-                    }, eventResult -> {
-                        // 成功的回复
-                        try {
-                            responseAck(request, eventResult.statusCode);
-                        } catch (SipException | InvalidArgumentException | ParseException e) {
-                            logger.error("[命令发送失败] 国标级联 录像控制: {}", e.getMessage());
-                        }
-                    });
+                    Device device1 = storager.queryVideoDevice(inviteInfo.getDeviceId());
+                    if (inviteInfo.getStreamInfo() != null) {
+                        cmder.playbackControlCmd(device1,inviteInfo.getStreamInfo(),new String(evt.getRequest().getRawContent()),eventResult -> {
+                            // 失败的回复
+                            try {
+                                responseAck(request, eventResult.statusCode, eventResult.msg);
+                            } catch (SipException | InvalidArgumentException | ParseException e) {
+                                logger.error("[命令发送失败] 国标级联 录像控制: {}", e.getMessage());
+                            }
+                        }, eventResult -> {
+                            // 成功的回复
+                            try {
+                                responseAck(request, eventResult.statusCode);
+                            } catch (SipException | InvalidArgumentException | ParseException e) {
+                                logger.error("[命令发送失败] 国标级联 录像控制: {}", e.getMessage());
+                            }
+                        });
+                    }
+
                 }
             }
         } catch (SipException e) {

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

@@ -69,6 +69,7 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
 
         RemoteAddressInfo remoteAddressInfo = SipUtils.getRemoteAddressFromRequest(request, userSetting.getSipUseSourceIpAsRemoteAddress());
         if (!device.getIp().equalsIgnoreCase(remoteAddressInfo.getIp()) || device.getPort() != remoteAddressInfo.getPort()) {
+            logger.info("[心跳] 设备{}地址变化, 远程地址为: {}:{}", device.getDeviceId(), remoteAddressInfo.getIp(), remoteAddressInfo.getPort());
             device.setPort(remoteAddressInfo.getPort());
             device.setHostAddress(remoteAddressInfo.getIp().concat(":").concat(String.valueOf(remoteAddressInfo.getPort())));
             device.setIp(remoteAddressInfo.getIp());

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

@@ -1,6 +1,7 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.cmd;
 
-import com.genersoft.iot.vmp.common.StreamInfo;
+import com.genersoft.iot.vmp.common.InviteInfo;
+import com.genersoft.iot.vmp.common.InviteSessionType;
 import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
@@ -12,6 +13,10 @@ import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
 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.ZlmHttpHookSubscribe;
+import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
+import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
+import com.genersoft.iot.vmp.service.IInviteStreamService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import gov.nist.javax.sip.message.SIPRequest;
@@ -58,6 +63,15 @@ public class MediaStatusNotifyMessageHandler extends SIPRequestProcessorParent i
     @Autowired
     private VideoStreamSessionManager sessionManager;
 
+    @Autowired
+    private ZlmHttpHookSubscribe subscribe;
+
+    @Autowired
+    private IInviteStreamService inviteStreamService;
+
+    @Autowired
+    private VideoStreamSessionManager streamSession;
+
     @Override
     public void afterPropertiesSet() throws Exception {
         notifyMessageHandler.addHandler(cmdType, this);
@@ -76,23 +90,24 @@ public class MediaStatusNotifyMessageHandler extends SIPRequestProcessorParent i
         String NotifyType =getText(rootElement, "NotifyType");
         if ("121".equals(NotifyType)){
             logger.info("[录像流]推送完毕,收到关流通知");
-            // 查询是设备
-            StreamInfo streamInfo = redisCatchStorage.queryDownload(null, null, null, callIdHeader.getCallId());
-            if (streamInfo != null) {
-                // 设置进度100%
-                streamInfo.setProgress(1);
-                redisCatchStorage.startDownload(streamInfo, callIdHeader.getCallId());
-            }
 
-            // 先从会话内查找
-            SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, callIdHeader.getCallId(), null);
-            if (ssrcTransaction != null) { // 兼容海康 媒体通知 消息from字段不是设备ID的问题
+            SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(null, null, callIdHeader.getCallId(), null);
+            if (ssrcTransaction != null) {
+                logger.info("[录像流]推送完毕,关流通知, device: {}, channelId: {}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
+                InviteInfo inviteInfo = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
+                if (inviteInfo.getStreamInfo() != null) {
+                    inviteInfo.getStreamInfo().setProgress(1);
+                    inviteStreamService.updateInviteInfo(inviteInfo);
+                }
 
                 try {
                     cmder.streamByeCmd(device, ssrcTransaction.getChannelId(), null, callIdHeader.getCallId());
                 } catch (InvalidArgumentException | ParseException  | SipException | SsrcTransactionNotFoundException e) {
                     logger.error("[录像流]推送完毕,收到关流通知, 发送BYE失败 {}", e.getMessage());
                 }
+                // 去除监听流注销自动停止下载的监听
+                HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcTransaction.getStream(), false, "rtsp", ssrcTransaction.getMediaServerId());
+                subscribe.removeSubscribe(hookSubscribe);
 
                 // 如果级联播放,需要给上级发送此通知 TODO 多个上级同时观看一个下级 可能存在停错的问题,需要将点播CallId进行上下级绑定
                 SendRtpItem sendRtpItem =  redisCatchStorage.querySendRTPServer(null, ssrcTransaction.getChannelId(), null, null);
@@ -108,6 +123,8 @@ public class MediaStatusNotifyMessageHandler extends SIPRequestProcessorParent i
                         logger.error("[命令发送失败] 国标级联 录像播放完毕: {}", e.getMessage());
                     }
                 }
+            }else {
+                logger.info("[录像流]推送完毕,关流通知, 但是未找到对应的下载信息");
             }
         }
     }

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

@@ -18,6 +18,10 @@ 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;
 import javax.sip.message.Response;
@@ -91,7 +95,7 @@ public class InviteResponseProcessor extends SIPResponseProcessorAbstract {
 				}
 				// 查看是否是来自设备的,此是回复
 
-				SipURI requestUri = sipLayer.getSipFactory().createAddressFactory().createSipURI(sdp.getOrigin().getUsername(), event.getRemoteIpAddress() + ":" + event.getRemotePort());
+				SipURI requestUri = SipFactory.getInstance().createAddressFactory().createSipURI(sdp.getOrigin().getUsername(), event.getRemoteIpAddress() + ":" + event.getRemotePort());
 				Request reqAck = headerProvider.createAckRequest(response.getLocalAddress().getHostAddress(), requestUri, response);
 
 				logger.info("[回复ack] {}-> {}:{} ", sdp.getOrigin().getUsername(), event.getRemoteIpAddress(), event.getRemotePort());

+ 7 - 6
src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java

@@ -54,7 +54,7 @@ public class SipUtils {
         return "z9hG4bK" + System.currentTimeMillis();
     }
 
-    public static UserAgentHeader createUserAgentHeader(SipFactory sipFactory, GitUtil gitUtil) throws PeerUnavailableException, ParseException {
+    public static UserAgentHeader createUserAgentHeader(GitUtil gitUtil) throws PeerUnavailableException, ParseException {
         List<String> agentParam = new ArrayList<>();
         agentParam.add("WVP-Pro ");
         if (gitUtil != null ) {
@@ -66,7 +66,7 @@ public class SipUtils {
                 agentParam.add(gitUtil.getCommitTime());
             }
         }
-        return sipFactory.createHeaderFactory().createUserAgentHeader(agentParam);
+        return SipFactory.getInstance().createHeaderFactory().createUserAgentHeader(agentParam);
     }
 
     public static String getNewFromTag(){
@@ -153,8 +153,9 @@ public class SipUtils {
         String remoteAddress;
         int remotePort;
         if (sipUseSourceIpAsRemoteAddress) {
-            remoteAddress = request.getRemoteAddress().getHostAddress();
-            remotePort = request.getRemotePort();
+            remoteAddress = request.getPeerPacketSourceAddress().getHostAddress();
+            remotePort = request.getPeerPacketSourcePort();
+
         }else {
             // 判断RPort是否改变,改变则说明路由nat信息变化,修改设备信息
             // 获取到通信地址等信息
@@ -162,8 +163,8 @@ public class SipUtils {
             remotePort = request.getTopmostViaHeader().getRPort();
             // 解析本地地址替代
             if (ObjectUtils.isEmpty(remoteAddress) || remotePort == -1) {
-                remoteAddress = request.getTopmostViaHeader().getHost();
-                remotePort = request.getTopmostViaHeader().getPort();
+                remoteAddress = request.getPeerPacketSourceAddress().getHostAddress();
+                remotePort = request.getPeerPacketSourcePort();
             }
         }
 

+ 15 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/annotation/MsgId.java

@@ -0,0 +1,15 @@
+package com.genersoft.iot.vmp.jt1078.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * @author QingtaiJiang
+ * @date 2023/4/27 18:31
+ * @email qingtaij@163.com
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface MsgId {
+    String id();
+}

+ 115 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/cmd/JT1078Template.java

@@ -0,0 +1,115 @@
+package com.genersoft.iot.vmp.jt1078.cmd;
+
+import com.genersoft.iot.vmp.jt1078.proc.entity.Cmd;
+import com.genersoft.iot.vmp.jt1078.proc.response.*;
+import com.genersoft.iot.vmp.jt1078.session.SessionManager;
+
+import java.util.Random;
+
+/**
+ * @author QingtaiJiang
+ * @date 2023/4/27 18:58
+ * @email qingtaij@163.com
+ */
+public class JT1078Template {
+
+    private final Random random = new Random();
+
+    private static final String H9101 = "9101";
+    private static final String H9102 = "9102";
+    private static final String H9201 = "9201";
+    private static final String H9202 = "9202";
+    private static final String H9205 = "9205";
+
+    private static final String H0001 = "0001";
+    private static final String H1205 = "1205";
+
+    /**
+     * 开启直播视频
+     *
+     * @param devId 设备号
+     * @param j9101 开启视频参数
+     */
+    public String startLive(String devId, J9101 j9101, Integer timeOut) {
+        Cmd cmd = new Cmd.Builder()
+                .setDevId(devId)
+                .setPackageNo(randomInt())
+                .setMsgId(H9101)
+                .setRespId(H0001)
+                .setRs(j9101)
+                .build();
+        return SessionManager.INSTANCE.request(cmd, timeOut);
+    }
+
+    /**
+     * 关闭直播视频
+     *
+     * @param devId 设备号
+     * @param j9102 关闭视频参数
+     */
+    public String stopLive(String devId, J9102 j9102, Integer timeOut) {
+        Cmd cmd = new Cmd.Builder()
+                .setDevId(devId)
+                .setPackageNo(randomInt())
+                .setMsgId(H9102)
+                .setRespId(H0001)
+                .setRs(j9102)
+                .build();
+        return SessionManager.INSTANCE.request(cmd, timeOut);
+    }
+
+    /**
+     * 查询音视频列表
+     *
+     * @param devId 设备号
+     * @param j9205 查询音视频列表
+     */
+    public String queryBackTime(String devId, J9205 j9205, Integer timeOut) {
+        Cmd cmd = new Cmd.Builder()
+                .setDevId(devId)
+                .setPackageNo(randomInt())
+                .setMsgId(H9205)
+                .setRespId(H1205)
+                .setRs(j9205)
+                .build();
+        return SessionManager.INSTANCE.request(cmd, timeOut);
+    }
+
+    /**
+     * 开启视频回放
+     *
+     * @param devId 设备号
+     * @param j9201 视频回放参数
+     */
+    public String startBackLive(String devId, J9201 j9201, Integer timeOut) {
+        Cmd cmd = new Cmd.Builder()
+                .setDevId(devId)
+                .setPackageNo(randomInt())
+                .setMsgId(H9201)
+                .setRespId(H1205)
+                .setRs(j9201)
+                .build();
+        return SessionManager.INSTANCE.request(cmd, timeOut);
+    }
+
+    /**
+     * 视频回放控制
+     *
+     * @param devId 设备号
+     * @param j9202 控制视频回放参数
+     */
+    public String controlBackLive(String devId, J9202 j9202, Integer timeOut) {
+        Cmd cmd = new Cmd.Builder()
+                .setDevId(devId)
+                .setPackageNo(randomInt())
+                .setMsgId(H9202)
+                .setRespId(H0001)
+                .setRs(j9202)
+                .build();
+        return SessionManager.INSTANCE.request(cmd, timeOut);
+    }
+
+    private Long randomInt() {
+        return (long) random.nextInt(1000) + 1;
+    }
+}

+ 146 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/codec/decode/Jt808Decoder.java

@@ -0,0 +1,146 @@
+package com.genersoft.iot.vmp.jt1078.codec.decode;
+
+import com.genersoft.iot.vmp.jt1078.proc.Header;
+import com.genersoft.iot.vmp.jt1078.proc.factory.CodecFactory;
+import com.genersoft.iot.vmp.jt1078.proc.request.Re;
+import com.genersoft.iot.vmp.jt1078.proc.response.Rs;
+import com.genersoft.iot.vmp.jt1078.session.Session;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.CompositeByteBuf;
+import io.netty.buffer.UnpooledByteBufAllocator;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author QingtaiJiang
+ * @date 2023/4/27 18:10
+ * @email qingtaij@163.com
+ */
+public class Jt808Decoder extends ByteToMessageDecoder {
+    private final static Logger log = LoggerFactory.getLogger(Jt808Decoder.class);
+
+    @Override
+    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
+        Session session = ctx.channel().attr(Session.KEY).get();
+        log.info("> {} hex:{}", session, ByteBufUtil.hexDump(in));
+
+        try {
+            ByteBuf buf = unEscapeAndCheck(in);
+
+            Header header = new Header();
+            header.setMsgId(ByteBufUtil.hexDump(buf.readSlice(2)));
+            header.setMsgPro(buf.readUnsignedShort());
+            if (header.is2019Version()) {
+                header.setVersion(buf.readUnsignedByte());
+                String devId = ByteBufUtil.hexDump(buf.readSlice(10));
+                header.setDevId(devId.replaceFirst("^0*", ""));
+            } else {
+                header.setDevId(ByteBufUtil.hexDump(buf.readSlice(6)).replaceFirst("^0*", ""));
+            }
+            header.setSn(buf.readUnsignedShort());
+
+            Re handler = CodecFactory.getHandler(header.getMsgId());
+            if (handler == null) {
+                log.error("get msgId is null {}", header.getMsgId());
+                return;
+            }
+            Rs decode = handler.decode(buf, header, session);
+            if (decode != null) {
+                out.add(decode);
+            }
+        } finally {
+            in.skipBytes(in.readableBytes());
+        }
+
+
+    }
+
+
+    /**
+     * 转义与验证校验码
+     *
+     * @param byteBuf 转义Buf
+     * @return 转义好的数据
+     */
+    public ByteBuf unEscapeAndCheck(ByteBuf byteBuf) throws Exception {
+        int low = byteBuf.readerIndex();
+        int high = byteBuf.writerIndex();
+        byte checkSum = 0;
+        int calculationCheckSum = 0;
+
+        byte aByte = byteBuf.getByte(high - 2);
+        byte protocolEscapeFlag7d = 0x7d;
+        //0x7d转义
+        byte protocolEscapeFlag01 = 0x01;
+        //0x7e转义
+        byte protocolEscapeFlag02 = 0x02;
+        if (aByte == protocolEscapeFlag7d) {
+            byte b2 = byteBuf.getByte(high - 1);
+            if (b2 == protocolEscapeFlag01) {
+                checkSum = protocolEscapeFlag7d;
+            } else if (b2 == protocolEscapeFlag02) {
+                checkSum = 0x7e;
+            } else {
+                log.error("转义1异常:{}", ByteBufUtil.hexDump(byteBuf));
+                throw new Exception("转义错误");
+            }
+            high = high - 2;
+        } else {
+            high = high - 1;
+            checkSum = byteBuf.getByte(high);
+        }
+        List<ByteBuf> bufList = new ArrayList<>();
+        int index = low;
+        while (index < high) {
+            byte b = byteBuf.getByte(index);
+            if (b == protocolEscapeFlag7d) {
+                byte c = byteBuf.getByte(index + 1);
+                if (c == protocolEscapeFlag01) {
+                    ByteBuf slice = slice0x01(byteBuf, low, index);
+                    bufList.add(slice);
+                    b = protocolEscapeFlag7d;
+                } else if (c == protocolEscapeFlag02) {
+                    ByteBuf slice = slice0x02(byteBuf, low, index);
+                    bufList.add(slice);
+                    b = 0x7e;
+                } else {
+                    log.error("转义2异常:{}", ByteBufUtil.hexDump(byteBuf));
+                    throw new Exception("转义错误");
+                }
+                index += 2;
+                low = index;
+            } else {
+                index += 1;
+            }
+            calculationCheckSum = calculationCheckSum ^ b;
+        }
+
+        if (calculationCheckSum == checkSum) {
+            if (bufList.size() == 0) {
+                return byteBuf.slice(low, high);
+            } else {
+                bufList.add(byteBuf.slice(low, high - low));
+                return new CompositeByteBuf(UnpooledByteBufAllocator.DEFAULT, false, bufList.size(), bufList);
+            }
+        } else {
+            log.info("{} 解析校验码:{}--计算校验码:{}", ByteBufUtil.hexDump(byteBuf), checkSum, calculationCheckSum);
+            throw new Exception("校验码错误!");
+        }
+    }
+
+
+    private ByteBuf slice0x01(ByteBuf buf, int low, int sign) {
+        return buf.slice(low, sign - low + 1);
+    }
+
+    private ByteBuf slice0x02(ByteBuf buf, int low, int sign) {
+        buf.setByte(sign, 0x7e);
+        return buf.slice(low, sign - low + 1);
+    }
+}

+ 33 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/codec/encode/Jt808Encoder.java

@@ -0,0 +1,33 @@
+package com.genersoft.iot.vmp.jt1078.codec.encode;
+
+
+import com.genersoft.iot.vmp.jt1078.proc.response.Rs;
+import com.genersoft.iot.vmp.jt1078.session.Session;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.MessageToByteEncoder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author QingtaiJiang
+ * @date 2023/4/27 18:10
+ * @email qingtaij@163.com
+ */
+public class Jt808Encoder extends MessageToByteEncoder<Rs> {
+    private final static Logger log = LoggerFactory.getLogger(Jt808Encoder.class);
+
+    @Override
+    protected void encode(ChannelHandlerContext ctx, Rs msg, ByteBuf out) throws Exception {
+        Session session = ctx.channel().attr(Session.KEY).get();
+
+        ByteBuf encode = Jt808EncoderCmd.encode(msg, session, session.nextSerialNo());
+        if(encode!=null){
+            log.info("< {} hex:{}", session, ByteBufUtil.hexDump(encode));
+            out.writeBytes(encode);
+        }
+    }
+
+
+}

+ 151 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/codec/encode/Jt808EncoderCmd.java

@@ -0,0 +1,151 @@
+package com.genersoft.iot.vmp.jt1078.codec.encode;
+
+import com.genersoft.iot.vmp.jt1078.annotation.MsgId;
+import com.genersoft.iot.vmp.jt1078.proc.Header;
+import com.genersoft.iot.vmp.jt1078.proc.entity.Cmd;
+import com.genersoft.iot.vmp.jt1078.proc.response.Rs;
+import com.genersoft.iot.vmp.jt1078.session.Session;
+import com.genersoft.iot.vmp.jt1078.util.Bin;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.CompositeByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.MessageToByteEncoder;
+import io.netty.util.ByteProcessor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.StringUtils;
+
+import java.util.LinkedList;
+
+/**
+ * @author QingtaiJiang
+ * @date 2023/4/27 18:25
+ * @email qingtaij@163.com
+ */
+public class Jt808EncoderCmd extends MessageToByteEncoder<Cmd> {
+    private final static Logger log = LoggerFactory.getLogger(Jt808EncoderCmd.class);
+
+    @Override
+    protected void encode(ChannelHandlerContext ctx, Cmd cmd, ByteBuf out) throws Exception {
+        Session session = ctx.channel().attr(Session.KEY).get();
+        Rs msg = cmd.getRs();
+        ByteBuf encode = encode(msg, session, cmd.getPackageNo().intValue());
+        if (encode != null) {
+            log.info("< {} hex:{}", session, ByteBufUtil.hexDump(encode));
+            out.writeBytes(encode);
+        }
+    }
+
+
+    public static ByteBuf encode(Rs msg, Session session, Integer packageNo) {
+        String id = msg.getClass().getAnnotation(MsgId.class).id();
+        if (!StringUtils.hasLength(id)) {
+            log.error("Not find msgId");
+            return null;
+        }
+
+        ByteBuf byteBuf = Unpooled.buffer();
+
+        byteBuf.writeBytes(ByteBufUtil.decodeHexDump(id));
+
+        ByteBuf encode = msg.encode();
+
+        Header header = msg.getHeader();
+        if (header == null) {
+            header = session.getHeader();
+        }
+
+        if (header.is2019Version()) {
+            // 消息体属性
+            byteBuf.writeShort(encode.readableBytes() | 1 << 14);
+
+            // 版本号
+            byteBuf.writeByte(header.getVersion());
+
+            // 终端手机号
+            byteBuf.writeBytes(ByteBufUtil.decodeHexDump(Bin.strHexPaddingLeft(header.getDevId(), 20)));
+        } else {
+            // 消息体属性
+            byteBuf.writeShort(encode.readableBytes());
+
+            byteBuf.writeBytes(ByteBufUtil.decodeHexDump(Bin.strHexPaddingLeft(header.getDevId(), 12)));
+        }
+
+        // 消息体流水号
+        byteBuf.writeShort(packageNo);
+
+        // 写入消息体
+        byteBuf.writeBytes(encode);
+
+        // 计算校验码,并反转义
+        byteBuf = escapeAndCheck0(byteBuf);
+        return byteBuf;
+    }
+
+
+    private static final ByteProcessor searcher = value -> !(value == 0x7d || value == 0x7e);
+
+    //转义与校验
+    public static ByteBuf escapeAndCheck0(ByteBuf source) {
+
+        sign(source);
+
+        int low = source.readerIndex();
+        int high = source.writerIndex();
+
+        LinkedList<ByteBuf> bufList = new LinkedList<>();
+        int mark, len;
+        while ((mark = source.forEachByte(low, high - low, searcher)) > 0) {
+
+            len = mark + 1 - low;
+            ByteBuf[] slice = slice(source, low, len);
+            bufList.add(slice[0]);
+            bufList.add(slice[1]);
+            low += len;
+        }
+
+        if (bufList.size() > 0) {
+            bufList.add(source.slice(low, high - low));
+        } else {
+            bufList.add(source);
+        }
+
+        ByteBuf delimiter = Unpooled.buffer(1, 1).writeByte(0x7e).retain();
+        bufList.addFirst(delimiter);
+        bufList.addLast(delimiter);
+
+        CompositeByteBuf byteBufLs = Unpooled.compositeBuffer(bufList.size());
+        byteBufLs.addComponents(true, bufList);
+        return byteBufLs;
+    }
+
+    public static void sign(ByteBuf buf) {
+        byte checkCode = bcc(buf);
+        buf.writeByte(checkCode);
+    }
+
+    public static byte bcc(ByteBuf byteBuf) {
+        byte cs = 0;
+        while (byteBuf.isReadable())
+            cs ^= byteBuf.readByte();
+        byteBuf.resetReaderIndex();
+        return cs;
+    }
+
+    protected static ByteBuf[] slice(ByteBuf byteBuf, int index, int length) {
+        byte first = byteBuf.getByte(index + length - 1);
+
+        ByteBuf[] byteBufList = new ByteBuf[2];
+        byteBufList[0] = byteBuf.retainedSlice(index, length);
+
+        if (first == 0x7d) {
+            byteBufList[1] = Unpooled.buffer(1, 1).writeByte(0x01);
+        } else {
+            byteBuf.setByte(index + length - 1, 0x7d);
+            byteBufList[1] = Unpooled.buffer(1, 1).writeByte(0x02);
+        }
+        return byteBufList;
+    }
+}

+ 72 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/codec/netty/Jt808Handler.java

@@ -0,0 +1,72 @@
+package com.genersoft.iot.vmp.jt1078.codec.netty;
+
+import com.genersoft.iot.vmp.jt1078.proc.response.Rs;
+import com.genersoft.iot.vmp.jt1078.session.Session;
+import com.genersoft.iot.vmp.jt1078.session.SessionManager;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.handler.timeout.IdleState;
+import io.netty.handler.timeout.IdleStateEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author QingtaiJiang
+ * @date 2023/4/27 18:14
+ * @email qingtaij@163.com
+ */
+public class Jt808Handler extends ChannelInboundHandlerAdapter {
+
+    private final static Logger log = LoggerFactory.getLogger(Jt808Handler.class);
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+        if (msg instanceof Rs) {
+            ctx.writeAndFlush(msg);
+        } else {
+            ctx.fireChannelRead(msg);
+        }
+    }
+
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) {
+        Channel channel = ctx.channel();
+        Session session = SessionManager.INSTANCE.newSession(channel);
+        channel.attr(Session.KEY).set(session);
+        log.info("> Tcp connect {}", session);
+    }
+
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) {
+        Session session = ctx.channel().attr(Session.KEY).get();
+        log.info("< Tcp disconnect {}", session);
+        ctx.close();
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) {
+        Session session = ctx.channel().attr(Session.KEY).get();
+        String message = e.getMessage();
+        if (message.toLowerCase().contains("Connection reset by peer".toLowerCase())) {
+            log.info("< exception{} {}", session, e.getMessage());
+        } else {
+            log.info("< exception{} {}", session, e.getMessage(), e);
+        }
+
+    }
+
+    @Override
+    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
+        if (evt instanceof IdleStateEvent) {
+            IdleStateEvent event = (IdleStateEvent) evt;
+            IdleState state = event.state();
+            if (state == IdleState.READER_IDLE || state == IdleState.WRITER_IDLE) {
+                Session session = ctx.channel().attr(Session.KEY).get();
+                log.warn("< Proactively disconnect{}", session);
+                ctx.close();
+            }
+        }
+    }
+
+}

+ 112 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/codec/netty/TcpServer.java

@@ -0,0 +1,112 @@
+package com.genersoft.iot.vmp.jt1078.codec.netty;
+
+import com.genersoft.iot.vmp.jt1078.codec.decode.Jt808Decoder;
+import com.genersoft.iot.vmp.jt1078.codec.encode.Jt808Encoder;
+import com.genersoft.iot.vmp.jt1078.codec.encode.Jt808EncoderCmd;
+import com.genersoft.iot.vmp.jt1078.proc.factory.CodecFactory;
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioChannelOption;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.codec.DelimiterBasedFrameDecoder;
+import io.netty.handler.timeout.IdleStateHandler;
+import io.netty.util.concurrent.Future;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author QingtaiJiang
+ * @date 2023/4/27 18:01
+ * @email qingtaij@163.com
+ */
+
+public class TcpServer {
+    private final static Logger log = LoggerFactory.getLogger(TcpServer.class);
+
+    private final Integer port;
+    private boolean isRunning = false;
+    private EventLoopGroup bossGroup = null;
+    private EventLoopGroup workerGroup = null;
+
+    private final ByteBuf DECODER_JT808 = Unpooled.wrappedBuffer(new byte[]{0x7e});
+
+    public TcpServer(Integer port) {
+        this.port = port;
+    }
+
+    private void startTcpServer() {
+        try {
+            CodecFactory.init();
+            this.bossGroup = new NioEventLoopGroup();
+            this.workerGroup = new NioEventLoopGroup();
+            ServerBootstrap bootstrap = new ServerBootstrap();
+            bootstrap.channel(NioServerSocketChannel.class);
+            bootstrap.group(bossGroup, workerGroup);
+
+            bootstrap.option(NioChannelOption.SO_BACKLOG, 1024)
+                    .option(NioChannelOption.SO_REUSEADDR, true)
+                    .childOption(NioChannelOption.TCP_NODELAY, true)
+                    .childHandler(new ChannelInitializer<NioSocketChannel>() {
+                        @Override
+                        public void initChannel(NioSocketChannel channel) {
+                            channel.pipeline()
+                                    .addLast(new IdleStateHandler(10, 0, 0, TimeUnit.MINUTES))
+                                    .addLast(new DelimiterBasedFrameDecoder(1024 * 2, DECODER_JT808))
+                                    .addLast(new Jt808Decoder())
+                                    .addLast(new Jt808Encoder())
+                                    .addLast(new Jt808EncoderCmd())
+                                    .addLast(new Jt808Handler());
+                        }
+                    });
+            ChannelFuture channelFuture = bootstrap.bind(port).sync();
+            // 监听设备TCP端口是否启动成功
+            channelFuture.addListener(future -> {
+                if (!future.isSuccess()) {
+                    log.error("Binding port:{} fail!  cause: {}", port, future.cause().getCause(), future.cause());
+                }
+            });
+            log.info("服务:JT808 Server 启动成功, port:{}", port);
+            channelFuture.channel().closeFuture().sync();
+        } catch (Exception e) {
+            log.warn("服务:JT808 Server 启动异常, port:{},{}", port, e.getMessage(), e);
+        } finally {
+            stop();
+        }
+    }
+
+    /**
+     * 开启一个新的线程,拉起来Netty
+     */
+    public synchronized void start() {
+        if (this.isRunning) {
+            log.warn("服务:JT808 Server 已经启动, port:{}", port);
+            return;
+        }
+        this.isRunning = true;
+        new Thread(this::startTcpServer).start();
+    }
+
+    public synchronized void stop() {
+        if (!this.isRunning) {
+            log.warn("服务:JT808 Server 已经停止, port:{}", port);
+        }
+        this.isRunning = false;
+        Future<?> future = this.bossGroup.shutdownGracefully();
+        if (!future.isSuccess()) {
+            log.warn("bossGroup 无法正常停止", future.cause());
+        }
+        future = this.workerGroup.shutdownGracefully();
+        if (!future.isSuccess()) {
+            log.warn("workerGroup 无法正常停止", future.cause());
+        }
+        log.warn("服务:JT808 Server 已经停止, port:{}", port);
+    }
+}

+ 30 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/config/JT1078AutoConfiguration.java

@@ -0,0 +1,30 @@
+package com.genersoft.iot.vmp.jt1078.config;
+
+import com.genersoft.iot.vmp.jt1078.cmd.JT1078Template;
+import com.genersoft.iot.vmp.jt1078.codec.netty.TcpServer;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
+
+/**
+ * @author QingtaiJiang
+ * @date 2023/4/27 19:35
+ * @email qingtaij@163.com
+ */
+@Order(Integer.MIN_VALUE)
+@Configuration
+@ConditionalOnProperty(value = "jt1078.enable", havingValue = "true")
+public class JT1078AutoConfiguration {
+
+    @Bean(initMethod = "start", destroyMethod = "stop")
+    public TcpServer jt1078Server(@Value("${jt1078.port}") Integer port) {
+        return new TcpServer(port);
+    }
+
+    @Bean
+    public JT1078Template jt1078Template() {
+        return new JT1078Template();
+    }
+}

+ 51 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/config/JT1078Controller.java

@@ -0,0 +1,51 @@
+package com.genersoft.iot.vmp.jt1078.config;
+
+import com.genersoft.iot.vmp.jt1078.cmd.JT1078Template;
+import com.genersoft.iot.vmp.jt1078.proc.response.*;
+import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+
+/**
+ * curl http://localhost:18080/api/jt1078/start/live/18864197066/1
+ *
+ * @author QingtaiJiang
+ * @date 2023/4/27 18:12
+ * @email qingtaij@163.com
+ */
+@ConditionalOnProperty(value = "jt1078.enable", havingValue = "true")
+@RestController
+@RequestMapping("/api/jt1078")
+public class JT1078Controller {
+
+    @Resource
+    JT1078Template jt1078Template;
+
+    /**
+     * jt1078Template 调用示例
+     */
+    @GetMapping("/start/live/{deviceId}/{channelId}")
+    public WVPResult<?> startLive(@PathVariable String deviceId, @PathVariable String channelId) {
+        J9101 j9101 = new J9101();
+        j9101.setChannel(Integer.valueOf(channelId));
+        j9101.setIp("192.168.1.1");
+        j9101.setRate(1);
+        j9101.setTcpPort(7618);
+        j9101.setUdpPort(7618);
+        j9101.setType(0);
+        // TODO 分配ZLM,获取IP、端口
+        String s = jt1078Template.startLive(deviceId, j9101, 6);
+        // TODO 设备响应成功后,封装拉流结果集
+        WVPResult<String> wvpResult = new WVPResult<>();
+        wvpResult.setCode(200);
+        wvpResult.setData(String.format("http://192.168.1.1/rtp/%s_%s.live.mp4", deviceId, channelId));
+        return wvpResult;
+    }
+
+}
+

+ 76 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/proc/Header.java

@@ -0,0 +1,76 @@
+package com.genersoft.iot.vmp.jt1078.proc;
+
+import com.genersoft.iot.vmp.jt1078.util.Bin;
+
+/**
+ * @author QingtaiJiang
+ * @date 2023/4/27 18:22
+ * @email qingtaij@163.com
+ */
+public class Header {
+    // 消息ID
+    String msgId;
+
+    // 消息体属性
+    Integer msgPro;
+
+    // 标识
+    String devId;
+
+    // 消息体流水号
+    Integer sn;
+
+    // 协议版本号
+    Short version = -1;
+
+
+    public String getMsgId() {
+        return msgId;
+    }
+
+    public void setMsgId(String msgId) {
+        this.msgId = msgId;
+    }
+
+    public Integer getMsgPro() {
+        return msgPro;
+    }
+
+    public void setMsgPro(Integer msgPro) {
+        this.msgPro = msgPro;
+    }
+
+    public String getDevId() {
+        return devId;
+    }
+
+    public void setDevId(String devId) {
+        this.devId = devId;
+    }
+
+    public Integer getSn() {
+        return sn;
+    }
+
+    public void setSn(Integer sn) {
+        this.sn = sn;
+    }
+
+    public Short getVersion() {
+        return version;
+    }
+
+    public void setVersion(Short version) {
+        this.version = version;
+    }
+
+    /**
+     * 判断是否是2019的版本
+     *
+     * @return true 2019后的版本。false 2013
+     */
+    public boolean is2019Version() {
+        return Bin.get(msgPro, 14);
+    }
+
+}

+ 116 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/proc/entity/Cmd.java

@@ -0,0 +1,116 @@
+package com.genersoft.iot.vmp.jt1078.proc.entity;
+
+import com.genersoft.iot.vmp.jt1078.proc.response.Rs;
+
+/**
+ * @author QingtaiJiang
+ * @date 2023/4/27 18:23
+ * @email qingtaij@163.com
+ */
+public class Cmd {
+    String devId;
+    Long packageNo;
+    String msgId;
+    String respId;
+    Rs rs;
+
+    public Cmd() {
+    }
+
+    public Cmd(Builder builder) {
+        this.devId = builder.devId;
+        this.packageNo = builder.packageNo;
+        this.msgId = builder.msgId;
+        this.respId = builder.respId;
+        this.rs = builder.rs;
+    }
+
+    public String getDevId() {
+        return devId;
+    }
+
+    public void setDevId(String devId) {
+        this.devId = devId;
+    }
+
+    public Long getPackageNo() {
+        return packageNo;
+    }
+
+    public void setPackageNo(Long packageNo) {
+        this.packageNo = packageNo;
+    }
+
+    public String getMsgId() {
+        return msgId;
+    }
+
+    public void setMsgId(String msgId) {
+        this.msgId = msgId;
+    }
+
+    public String getRespId() {
+        return respId;
+    }
+
+    public void setRespId(String respId) {
+        this.respId = respId;
+    }
+
+    public Rs getRs() {
+        return rs;
+    }
+
+    public void setRs(Rs rs) {
+        this.rs = rs;
+    }
+
+    public static class Builder {
+        String devId;
+        Long packageNo;
+        String msgId;
+        String respId;
+        Rs rs;
+
+        public Builder setDevId(String devId) {
+            this.devId = devId.replaceFirst("^0*", "");
+            return this;
+        }
+
+        public Builder setPackageNo(Long packageNo) {
+            this.packageNo = packageNo;
+            return this;
+        }
+
+        public Builder setMsgId(String msgId) {
+            this.msgId = msgId;
+            return this;
+        }
+
+        public Builder setRespId(String respId) {
+            this.respId = respId;
+            return this;
+        }
+
+        public Builder setRs(Rs re) {
+            this.rs = re;
+            return this;
+        }
+
+        public Cmd build() {
+            return new Cmd(this);
+        }
+    }
+
+
+    @Override
+    public String toString() {
+        return "Cmd{" +
+                "devId='" + devId + '\'' +
+                ", packageNo=" + packageNo +
+                ", msgId='" + msgId + '\'' +
+                ", respId='" + respId + '\'' +
+                ", rs=" + rs +
+                '}';
+    }
+}

+ 44 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/proc/factory/CodecFactory.java

@@ -0,0 +1,44 @@
+package com.genersoft.iot.vmp.jt1078.proc.factory;
+
+import com.genersoft.iot.vmp.jt1078.annotation.MsgId;
+import com.genersoft.iot.vmp.jt1078.proc.request.Re;
+import com.genersoft.iot.vmp.jt1078.util.ClassUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author QingtaiJiang
+ * @date 2023/4/27 18:29
+ * @email qingtaij@163.com
+ */
+
+public class CodecFactory {
+    private final static Logger log = LoggerFactory.getLogger(CodecFactory.class);
+
+    private static Map<String, Class<?>> protocolHash;
+
+    public static void init() {
+        protocolHash = new HashMap<>();
+        List<Class<?>> classList = ClassUtil.getClassList("com.genersoft.iot.vmp.jt1078.proc", MsgId.class);
+        for (Class<?> handlerClass : classList) {
+            String id = handlerClass.getAnnotation(MsgId.class).id();
+            protocolHash.put(id, handlerClass);
+        }
+        if (log.isDebugEnabled()) {
+            log.debug("消息ID缓存表 protocolHash:{}", protocolHash);
+        }
+    }
+
+    public static Re getHandler(String msgId) {
+        Class<?> aClass = protocolHash.get(msgId);
+        Object bean = ClassUtil.getBean(aClass);
+        if (bean instanceof Re) {
+            return (Re) bean;
+        }
+        return null;
+    }
+}

+ 50 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0001.java

@@ -0,0 +1,50 @@
+package com.genersoft.iot.vmp.jt1078.proc.request;
+
+import com.alibaba.fastjson2.JSON;
+import com.genersoft.iot.vmp.jt1078.annotation.MsgId;
+import com.genersoft.iot.vmp.jt1078.proc.Header;
+import com.genersoft.iot.vmp.jt1078.proc.response.Rs;
+import com.genersoft.iot.vmp.jt1078.session.Session;
+import com.genersoft.iot.vmp.jt1078.session.SessionManager;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+
+/**
+ * 终端通用应答
+ *
+ * @author QingtaiJiang
+ * @date 2023/4/27 18:04
+ * @email qingtaij@163.com
+ */
+@MsgId(id = "0001")
+public class J0001 extends Re {
+    int respNo;
+    String respId;
+    int result;
+
+    @Override
+    protected Rs decode0(ByteBuf buf, Header header, Session session) {
+        respNo = buf.readUnsignedShort();
+        respId = ByteBufUtil.hexDump(buf.readSlice(2));
+        result = buf.readUnsignedByte();
+        return null;
+    }
+
+    @Override
+    protected Rs handler(Header header, Session session) {
+        SessionManager.INSTANCE.response(header.getDevId(), "0001", (long) respNo, JSON.toJSONString(this));
+        return null;
+    }
+
+    public int getRespNo() {
+        return respNo;
+    }
+
+    public String getRespId() {
+        return respId;
+    }
+
+    public int getResult() {
+        return result;
+    }
+}

+ 32 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0002.java

@@ -0,0 +1,32 @@
+package com.genersoft.iot.vmp.jt1078.proc.request;
+
+import com.genersoft.iot.vmp.jt1078.annotation.MsgId;
+import com.genersoft.iot.vmp.jt1078.proc.Header;
+import com.genersoft.iot.vmp.jt1078.proc.response.J8001;
+import com.genersoft.iot.vmp.jt1078.proc.response.Rs;
+import com.genersoft.iot.vmp.jt1078.session.Session;
+import io.netty.buffer.ByteBuf;
+
+/**
+ * 终端心跳
+ *
+ * @author QingtaiJiang
+ * @date 2023/4/27 18:04
+ * @email qingtaij@163.com
+ */
+@MsgId(id = "0002")
+public class J0002 extends Re {
+    @Override
+    protected Rs decode0(ByteBuf buf, Header header, Session session) {
+        return null;
+    }
+
+    @Override
+    protected Rs handler(Header header, Session session) {
+        J8001 j8001 = new J8001();
+        j8001.setRespNo(header.getSn());
+        j8001.setRespId(header.getMsgId());
+        j8001.setResult(J8001.SUCCESS);
+        return j8001;
+    }
+}

+ 27 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0004.java

@@ -0,0 +1,27 @@
+package com.genersoft.iot.vmp.jt1078.proc.request;
+
+import com.genersoft.iot.vmp.jt1078.annotation.MsgId;
+import com.genersoft.iot.vmp.jt1078.proc.Header;
+import com.genersoft.iot.vmp.jt1078.proc.response.Rs;
+import com.genersoft.iot.vmp.jt1078.session.Session;
+import io.netty.buffer.ByteBuf;
+
+/**
+ * 查询服务器时间
+ *
+ * @author QingtaiJiang
+ * @date 2023/4/27 18:06
+ * @email qingtaij@163.com
+ */
+@MsgId(id = "0004")
+public class J0004 extends Re {
+    @Override
+    protected Rs decode0(ByteBuf buf, Header header, Session session) {
+        return null;
+    }
+
+    @Override
+    protected Rs handler(Header header, Session session) {
+        return null;
+    }
+}

+ 56 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0100.java

@@ -0,0 +1,56 @@
+package com.genersoft.iot.vmp.jt1078.proc.request;
+
+import com.genersoft.iot.vmp.jt1078.annotation.MsgId;
+import com.genersoft.iot.vmp.jt1078.proc.Header;
+import com.genersoft.iot.vmp.jt1078.proc.response.J8100;
+import com.genersoft.iot.vmp.jt1078.proc.response.Rs;
+import com.genersoft.iot.vmp.jt1078.session.Session;
+import io.netty.buffer.ByteBuf;
+
+/**
+ * 终端注册
+ *
+ * @author QingtaiJiang
+ * @date 2023/4/27 18:06
+ * @email qingtaij@163.com
+ */
+@MsgId(id = "0100")
+public class J0100 extends Re {
+
+    private int provinceId;
+
+    private int cityId;
+
+    private String makerId;
+
+    private String deviceModel;
+
+    private String deviceId;
+
+    private int plateColor;
+
+    private String plateNo;
+
+    @Override
+    protected Rs decode0(ByteBuf buf, Header header, Session session) {
+        Short version = header.getVersion();
+        provinceId = buf.readUnsignedShort();
+        if (version > 1) {
+            cityId = buf.readUnsignedShort();
+            // decode as 2019
+        } else {
+            int i = buf.readUnsignedShort();
+            // decode as 2013
+        }
+        return null;
+    }
+
+    @Override
+    protected Rs handler(Header header, Session session) {
+        J8100 j8100 = new J8100();
+        j8100.setRespNo(header.getSn());
+        j8100.setResult(J8100.SUCCESS);
+        j8100.setCode("WVP_YYDS");
+        return j8100;
+    }
+}

+ 36 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0102.java

@@ -0,0 +1,36 @@
+package com.genersoft.iot.vmp.jt1078.proc.request;
+
+import com.genersoft.iot.vmp.jt1078.annotation.MsgId;
+import com.genersoft.iot.vmp.jt1078.proc.Header;
+import com.genersoft.iot.vmp.jt1078.proc.response.J8001;
+import com.genersoft.iot.vmp.jt1078.proc.response.Rs;
+import com.genersoft.iot.vmp.jt1078.session.Session;
+import io.netty.buffer.ByteBuf;
+
+/**
+ * 终端鉴权
+ *
+ * @author QingtaiJiang
+ * @date 2023/4/27 18:06
+ * @email qingtaij@163.com
+ */
+@MsgId(id = "0102")
+public class J0102 extends Re {
+    @Override
+    protected Rs decode0(ByteBuf buf, Header header, Session session) {
+        int lenCode = buf.readUnsignedByte();
+//        String code = buf.readCharSequence(lenCode, CharsetUtil.UTF_8).toString();
+        // if 2019 to decode next
+        return null;
+    }
+
+    @Override
+    protected Rs handler(Header header, Session session) {
+        J8001 j8001 = new J8001();
+        j8001.setRespNo(header.getSn());
+        j8001.setRespId(header.getMsgId());
+        j8001.setResult(J8001.SUCCESS);
+        return j8001;
+    }
+
+}

+ 32 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0200.java

@@ -0,0 +1,32 @@
+package com.genersoft.iot.vmp.jt1078.proc.request;
+
+import com.genersoft.iot.vmp.jt1078.annotation.MsgId;
+import com.genersoft.iot.vmp.jt1078.proc.Header;
+import com.genersoft.iot.vmp.jt1078.proc.response.J8001;
+import com.genersoft.iot.vmp.jt1078.proc.response.Rs;
+import com.genersoft.iot.vmp.jt1078.session.Session;
+import io.netty.buffer.ByteBuf;
+
+/**
+ * 实时消息上报
+ *
+ * @author QingtaiJiang
+ * @date 2023/4/27 18:06
+ * @email qingtaij@163.com
+ */
+@MsgId(id = "0200")
+public class J0200 extends Re {
+    @Override
+    protected Rs decode0(ByteBuf buf, Header header, Session session) {
+        return null;
+    }
+
+    @Override
+    protected Rs handler(Header header, Session session) {
+        J8001 j8001 = new J8001();
+        j8001.setRespNo(header.getSn());
+        j8001.setRespId(header.getMsgId());
+        j8001.setResult(J8001.SUCCESS);
+        return j8001;
+    }
+}

+ 190 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J1205.java

@@ -0,0 +1,190 @@
+package com.genersoft.iot.vmp.jt1078.proc.request;
+
+import com.alibaba.fastjson2.JSON;
+import com.genersoft.iot.vmp.jt1078.annotation.MsgId;
+import com.genersoft.iot.vmp.jt1078.proc.Header;
+import com.genersoft.iot.vmp.jt1078.proc.response.J8001;
+import com.genersoft.iot.vmp.jt1078.proc.response.Rs;
+import com.genersoft.iot.vmp.jt1078.session.Session;
+import com.genersoft.iot.vmp.jt1078.session.SessionManager;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 终端上传音视频资源列表
+ *
+ * @author QingtaiJiang
+ * @date 2023/4/28 10:36
+ * @email qingtaij@163.com
+ */
+@MsgId(id = "1205")
+public class J1205 extends Re {
+    Integer respNo;
+
+    private List<JRecordItem> recordList = new ArrayList<JRecordItem>();
+
+    @Override
+    protected Rs decode0(ByteBuf buf, Header header, Session session) {
+        respNo = buf.readUnsignedShort();
+        long size = buf.readUnsignedInt();
+
+        for (int i = 0; i < size; i++) {
+            JRecordItem item = new JRecordItem();
+            item.setChannelId(buf.readUnsignedByte());
+            item.setStartTime(ByteBufUtil.hexDump(buf.readSlice(6)));
+            item.setEndTime(ByteBufUtil.hexDump(buf.readSlice(6)));
+            item.setWarn(buf.readLong());
+            item.setMediaType(buf.readUnsignedByte());
+            item.setStreamType(buf.readUnsignedByte());
+            item.setStorageType(buf.readUnsignedByte());
+            item.setSize(buf.readUnsignedInt());
+            recordList.add(item);
+        }
+
+        return null;
+    }
+
+    @Override
+    protected Rs handler(Header header, Session session) {
+        SessionManager.INSTANCE.response(header.getDevId(), "1205", (long) respNo, JSON.toJSONString(this));
+
+        J8001 j8001 = new J8001();
+        j8001.setRespNo(header.getSn());
+        j8001.setRespId(header.getMsgId());
+        j8001.setResult(J8001.SUCCESS);
+        return j8001;
+    }
+
+
+    public Integer getRespNo() {
+        return respNo;
+    }
+
+    public void setRespNo(Integer respNo) {
+        this.respNo = respNo;
+    }
+
+    public List<JRecordItem> getRecordList() {
+        return recordList;
+    }
+
+    public void setRecordList(List<JRecordItem> recordList) {
+        this.recordList = recordList;
+    }
+
+    public static class JRecordItem {
+
+        // 逻辑通道号
+        private int channelId;
+
+        // 开始时间
+        private String startTime;
+
+        // 结束时间
+        private String endTime;
+
+        // 报警标志
+        private long warn;
+
+        // 音视频资源类型
+        private int mediaType;
+
+        // 码流类型
+        private int streamType = 1;
+
+        // 存储器类型
+        private int storageType;
+
+        // 文件大小
+        private long size;
+
+        public int getChannelId() {
+            return channelId;
+        }
+
+        public void setChannelId(int channelId) {
+            this.channelId = channelId;
+        }
+
+        public String getStartTime() {
+            return startTime;
+        }
+
+        public void setStartTime(String startTime) {
+            this.startTime = startTime;
+        }
+
+        public String getEndTime() {
+            return endTime;
+        }
+
+        public void setEndTime(String endTime) {
+            this.endTime = endTime;
+        }
+
+        public long getWarn() {
+            return warn;
+        }
+
+        public void setWarn(long warn) {
+            this.warn = warn;
+        }
+
+        public int getMediaType() {
+            return mediaType;
+        }
+
+        public void setMediaType(int mediaType) {
+            this.mediaType = mediaType;
+        }
+
+        public int getStreamType() {
+            return streamType;
+        }
+
+        public void setStreamType(int streamType) {
+            this.streamType = streamType;
+        }
+
+        public int getStorageType() {
+            return storageType;
+        }
+
+        public void setStorageType(int storageType) {
+            this.storageType = storageType;
+        }
+
+        public long getSize() {
+            return size;
+        }
+
+        public void setSize(long size) {
+            this.size = size;
+        }
+
+        @Override
+        public String toString() {
+            return "JRecordItem{" +
+                    "channelId=" + channelId +
+                    ", startTime='" + startTime + '\'' +
+                    ", endTime='" + endTime + '\'' +
+                    ", warn=" + warn +
+                    ", mediaType=" + mediaType +
+                    ", streamType=" + streamType +
+                    ", storageType=" + storageType +
+                    ", size=" + size +
+                    '}';
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "J1205{" +
+                "respNo=" + respNo +
+                ", recordList=" + recordList +
+                '}';
+    }
+}

+ 40 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/Re.java

@@ -0,0 +1,40 @@
+package com.genersoft.iot.vmp.jt1078.proc.request;
+
+import com.genersoft.iot.vmp.jt1078.proc.Header;
+import com.genersoft.iot.vmp.jt1078.proc.response.Rs;
+import com.genersoft.iot.vmp.jt1078.session.Session;
+import io.netty.buffer.ByteBuf;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.StringUtils;
+
+/**
+ * @author QingtaiJiang
+ * @date 2023/4/27 18:50
+ * @email qingtaij@163.com
+ */
+public abstract class Re {
+    private final static Logger log = LoggerFactory.getLogger(Re.class);
+
+    protected abstract Rs decode0(ByteBuf buf, Header header, Session session);
+
+    protected abstract Rs handler(Header header, Session session);
+
+    public Rs decode(ByteBuf buf, Header header, Session session) {
+        if (session != null && !StringUtils.hasLength(session.getDevId())) {
+            session.register(header.getDevId(), (int) header.getVersion(), header);
+        }
+        Rs rs = decode0(buf, header, session);
+        Rs rsHand = handler(header, session);
+        if (rs == null && rsHand != null) {
+            rs = rsHand;
+        } else if (rs != null && rsHand != null) {
+            log.warn("decode0:{} 与 handler:{} 返回值冲突,采用decode0返回值", rs, rsHand);
+        }
+        if (rs != null) {
+            rs.setHeader(header);
+        }
+
+        return rs;
+    }
+}

+ 43 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8001.java

@@ -0,0 +1,43 @@
+package com.genersoft.iot.vmp.jt1078.proc.response;
+
+import com.genersoft.iot.vmp.jt1078.annotation.MsgId;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
+
+/**
+ * @author QingtaiJiang
+ * @date 2023/4/27 18:48
+ * @email qingtaij@163.com
+ */
+@MsgId(id = "8001")
+public class J8001 extends Rs {
+    public static final Integer SUCCESS = 0;
+
+    Integer respNo;
+    String respId;
+    Integer result;
+
+    @Override
+    public ByteBuf encode() {
+        ByteBuf buffer = Unpooled.buffer();
+        buffer.writeShort(respNo);
+        buffer.writeBytes(ByteBufUtil.decodeHexDump(respId));
+        buffer.writeByte(result);
+
+        return buffer;
+    }
+
+
+    public void setRespNo(Integer respNo) {
+        this.respNo = respNo;
+    }
+
+    public void setRespId(String respId) {
+        this.respId = respId;
+    }
+
+    public void setResult(Integer result) {
+        this.result = result;
+    }
+}

+ 41 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8100.java

@@ -0,0 +1,41 @@
+package com.genersoft.iot.vmp.jt1078.proc.response;
+
+import com.genersoft.iot.vmp.jt1078.annotation.MsgId;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.util.CharsetUtil;
+
+/**
+ * @author QingtaiJiang
+ * @date 2023/4/27 18:40
+ * @email qingtaij@163.com
+ */
+@MsgId(id = "8100")
+public class J8100 extends Rs {
+    public static final Integer SUCCESS = 0;
+
+    Integer respNo;
+    Integer result;
+    String code;
+
+    @Override
+    public ByteBuf encode() {
+        ByteBuf buffer = Unpooled.buffer();
+        buffer.writeShort(respNo);
+        buffer.writeByte(result);
+        buffer.writeCharSequence(code, CharsetUtil.UTF_8);
+        return buffer;
+    }
+
+    public void setRespNo(Integer respNo) {
+        this.respNo = respNo;
+    }
+
+    public void setResult(Integer result) {
+        this.result = result;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+}

+ 112 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9101.java

@@ -0,0 +1,112 @@
+package com.genersoft.iot.vmp.jt1078.proc.response;
+
+import com.genersoft.iot.vmp.jt1078.annotation.MsgId;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.util.CharsetUtil;
+
+/**
+ * 实时音视频传输请求
+ *
+ * @author QingtaiJiang
+ * @date 2023/4/27 18:25
+ * @email qingtaij@163.com
+ */
+@MsgId(id = "9101")
+public class J9101 extends Rs {
+    String ip;
+
+    // TCP端口
+    Integer tcpPort;
+
+    // UDP端口
+    Integer udpPort;
+
+    // 逻辑通道号
+    Integer channel;
+
+    // 数据类型
+    /**
+     * 0:音视频,1:视频,2:双向对讲,3:监听,4:中心广播,5:透传
+     */
+    Integer type;
+
+    // 码流类型
+    /**
+     * 0:主码流,1:子码流
+     */
+    Integer rate;
+
+    @Override
+    public ByteBuf encode() {
+        ByteBuf buffer = Unpooled.buffer();
+        buffer.writeByte(ip.getBytes().length);
+        buffer.writeCharSequence(ip, CharsetUtil.UTF_8);
+        buffer.writeShort(tcpPort);
+        buffer.writeShort(udpPort);
+        buffer.writeByte(channel);
+        buffer.writeByte(type);
+        buffer.writeByte(rate);
+        return buffer;
+    }
+
+    public String getIp() {
+        return ip;
+    }
+
+    public void setIp(String ip) {
+        this.ip = ip;
+    }
+
+    public Integer getTcpPort() {
+        return tcpPort;
+    }
+
+    public void setTcpPort(Integer tcpPort) {
+        this.tcpPort = tcpPort;
+    }
+
+    public Integer getUdpPort() {
+        return udpPort;
+    }
+
+    public void setUdpPort(Integer udpPort) {
+        this.udpPort = udpPort;
+    }
+
+    public Integer getChannel() {
+        return channel;
+    }
+
+    public void setChannel(Integer channel) {
+        this.channel = channel;
+    }
+
+    public Integer getType() {
+        return type;
+    }
+
+    public void setType(Integer type) {
+        this.type = type;
+    }
+
+    public Integer getRate() {
+        return rate;
+    }
+
+    public void setRate(Integer rate) {
+        this.rate = rate;
+    }
+
+    @Override
+    public String toString() {
+        return "J9101{" +
+                "ip='" + ip + '\'' +
+                ", tcpPort=" + tcpPort +
+                ", udpPort=" + udpPort +
+                ", channel=" + channel +
+                ", type=" + type +
+                ", rate=" + rate +
+                '}';
+    }
+}

+ 99 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9102.java

@@ -0,0 +1,99 @@
+package com.genersoft.iot.vmp.jt1078.proc.response;
+
+import com.genersoft.iot.vmp.jt1078.annotation.MsgId;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+
+/**
+ * 音视频实时传输控制
+ *
+ * @author QingtaiJiang
+ * @date 2023/4/27 18:49
+ * @email qingtaij@163.com
+ */
+@MsgId(id = "9102")
+public class J9102 extends Rs {
+
+    // 通道号
+    Integer channel;
+
+    // 控制指令
+    /**
+     * 0:关闭音视频传输指令;
+     * 1:切换码流(增加暂停和继续);
+     * 2:暂停该通道所有流的发送;
+     * 3:恢复暂停前流的发送,与暂停前的流类型一致;
+     * 4:关闭双向对讲
+     */
+    Integer command;
+
+    // 数据类型
+    /**
+     * 0:关闭该通道有关的音视频数据;
+     * 1:只关闭该通道有关的音频,保留该通道
+     * 有关的视频;
+     * 2:只关闭该通道有关的视频,保留该通道
+     * 有关的音频
+     */
+    Integer closeType;
+
+    // 数据类型
+    /**
+     * 0:主码流;
+     * 1:子码流
+     */
+    Integer streamType;
+
+    @Override
+    public ByteBuf encode() {
+        ByteBuf buffer = Unpooled.buffer();
+        buffer.writeByte(channel);
+        buffer.writeByte(command);
+        buffer.writeByte(closeType);
+        buffer.writeByte(streamType);
+        return buffer;
+    }
+
+
+    public Integer getChannel() {
+        return channel;
+    }
+
+    public void setChannel(Integer channel) {
+        this.channel = channel;
+    }
+
+    public Integer getCommand() {
+        return command;
+    }
+
+    public void setCommand(Integer command) {
+        this.command = command;
+    }
+
+    public Integer getCloseType() {
+        return closeType;
+    }
+
+    public void setCloseType(Integer closeType) {
+        this.closeType = closeType;
+    }
+
+    public Integer getStreamType() {
+        return streamType;
+    }
+
+    public void setStreamType(Integer streamType) {
+        this.streamType = streamType;
+    }
+
+    @Override
+    public String toString() {
+        return "J9102{" +
+                "channel=" + channel +
+                ", command=" + command +
+                ", closeType=" + closeType +
+                ", streamType=" + streamType +
+                '}';
+    }
+}

+ 173 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9201.java

@@ -0,0 +1,173 @@
+package com.genersoft.iot.vmp.jt1078.proc.response;
+
+import com.genersoft.iot.vmp.jt1078.annotation.MsgId;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
+import io.netty.util.CharsetUtil;
+
+/**
+ * 回放请求
+ *
+ * @author QingtaiJiang
+ * @date 2023/4/28 10:37
+ * @email qingtaij@163.com
+ */
+@MsgId(id = "9201")
+public class J9201 extends Rs {
+    // 服务器IP地址
+    private String ip;
+
+    // 实时视频服务器TCP端口号
+    private int tcpPort;
+
+    // 实时视频服务器UDP端口号
+    private int udpPort;
+
+    // 逻辑通道号
+    private int channel;
+
+    // 音视频资源类型:0.音视频 1.音频 2.视频 3.视频或音视频
+    private int type;
+
+    // 码流类型:0.所有码流 1.主码流 2.子码流(如果此通道只传输音频,此字段置0)
+    private int rate;
+
+    // 存储器类型:0.所有存储器 1.主存储器 2.灾备存储器"
+    private int storageType;
+
+    // 回放方式:0.正常回放 1.快进回放 2.关键帧快退回放 3.关键帧播放 4.单帧上传
+    private int playbackType;
+
+    // 快进或快退倍数:0.无效 1.1倍 2.2倍 3.4倍 4.8倍 5.16倍 (回放控制为1和2时,此字段内容有效,否则置0)
+    private int playbackSpeed;
+
+    // 开始时间YYMMDDHHMMSS,回放方式为4时,该字段表示单帧上传时间
+    private String startTime;
+
+    // 结束时间YYMMDDHHMMSS,回放方式为4时,该字段无效,为0表示一直回放
+    private String endTime;
+
+    @Override
+    public ByteBuf encode() {
+        ByteBuf buffer = Unpooled.buffer();
+        buffer.writeByte(ip.getBytes().length);
+        buffer.writeCharSequence(ip, CharsetUtil.UTF_8);
+        buffer.writeShort(tcpPort);
+        buffer.writeShort(udpPort);
+        buffer.writeByte(channel);
+        buffer.writeByte(type);
+        buffer.writeByte(rate);
+        buffer.writeByte(storageType);
+        buffer.writeByte(playbackType);
+        buffer.writeByte(playbackSpeed);
+        buffer.writeBytes(ByteBufUtil.decodeHexDump(startTime));
+        buffer.writeBytes(ByteBufUtil.decodeHexDump(endTime));
+        return buffer;
+    }
+
+    public String getIp() {
+        return ip;
+    }
+
+    public void setIp(String ip) {
+        this.ip = ip;
+    }
+
+    public int getTcpPort() {
+        return tcpPort;
+    }
+
+    public void setTcpPort(int tcpPort) {
+        this.tcpPort = tcpPort;
+    }
+
+    public int getUdpPort() {
+        return udpPort;
+    }
+
+    public void setUdpPort(int udpPort) {
+        this.udpPort = udpPort;
+    }
+
+    public int getChannel() {
+        return channel;
+    }
+
+    public void setChannel(int channel) {
+        this.channel = channel;
+    }
+
+    public int getType() {
+        return type;
+    }
+
+    public void setType(int type) {
+        this.type = type;
+    }
+
+    public int getRate() {
+        return rate;
+    }
+
+    public void setRate(int rate) {
+        this.rate = rate;
+    }
+
+    public int getStorageType() {
+        return storageType;
+    }
+
+    public void setStorageType(int storageType) {
+        this.storageType = storageType;
+    }
+
+    public int getPlaybackType() {
+        return playbackType;
+    }
+
+    public void setPlaybackType(int playbackType) {
+        this.playbackType = playbackType;
+    }
+
+    public int getPlaybackSpeed() {
+        return playbackSpeed;
+    }
+
+    public void setPlaybackSpeed(int playbackSpeed) {
+        this.playbackSpeed = playbackSpeed;
+    }
+
+    public String getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(String startTime) {
+        this.startTime = startTime;
+    }
+
+    public String getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(String endTime) {
+        this.endTime = endTime;
+    }
+
+    @Override
+    public String toString() {
+        return "J9201{" +
+                "ip='" + ip + '\'' +
+                ", tcpPort=" + tcpPort +
+                ", udpPort=" + udpPort +
+                ", channel=" + channel +
+                ", type=" + type +
+                ", rate=" + rate +
+                ", storageType=" + storageType +
+                ", playbackType=" + playbackType +
+                ", playbackSpeed=" + playbackSpeed +
+                ", startTime='" + startTime + '\'' +
+                ", endTime='" + endTime + '\'' +
+                '}';
+    }
+}

+ 80 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9202.java

@@ -0,0 +1,80 @@
+package com.genersoft.iot.vmp.jt1078.proc.response;
+
+import com.genersoft.iot.vmp.jt1078.annotation.MsgId;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
+
+/**
+ * 平台下发远程录像回放控制
+ *
+ * @author QingtaiJiang
+ * @date 2023/4/28 10:37
+ * @email qingtaij@163.com
+ */
+@MsgId(id = "9202")
+public class J9202 extends Rs {
+    // 逻辑通道号
+    private int channel;
+
+    // 回放控制:0.开始回放 1.暂停回放 2.结束回放 3.快进回放 4.关键帧快退回放 5.拖动回放 6.关键帧播放
+    private int playbackType;
+
+    // 快进或快退倍数:0.无效 1.1倍 2.2倍 3.4倍 4.8倍 5.16倍 (回放控制为3和4时,此字段内容有效,否则置0)
+    private int playbackSpeed;
+
+    // 拖动回放位置(YYMMDDHHMMSS,回放控制为5时,此字段有效)
+    private String playbackTime;
+
+    @Override
+    public ByteBuf encode() {
+        ByteBuf buffer = Unpooled.buffer();
+        buffer.writeByte(channel);
+        buffer.writeByte(playbackType);
+        buffer.writeByte(playbackSpeed);
+        buffer.writeBytes(ByteBufUtil.decodeHexDump(playbackTime));
+        return buffer;
+    }
+
+    public int getChannel() {
+        return channel;
+    }
+
+    public void setChannel(int channel) {
+        this.channel = channel;
+    }
+
+    public int getPlaybackType() {
+        return playbackType;
+    }
+
+    public void setPlaybackType(int playbackType) {
+        this.playbackType = playbackType;
+    }
+
+    public int getPlaybackSpeed() {
+        return playbackSpeed;
+    }
+
+    public void setPlaybackSpeed(int playbackSpeed) {
+        this.playbackSpeed = playbackSpeed;
+    }
+
+    public String getPlaybackTime() {
+        return playbackTime;
+    }
+
+    public void setPlaybackTime(String playbackTime) {
+        this.playbackTime = playbackTime;
+    }
+
+    @Override
+    public String toString() {
+        return "J9202{" +
+                "channel=" + channel +
+                ", playbackType=" + playbackType +
+                ", playbackSpeed=" + playbackSpeed +
+                ", playbackTime='" + playbackTime + '\'' +
+                '}';
+    }
+}

+ 94 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9205.java

@@ -0,0 +1,94 @@
+package com.genersoft.iot.vmp.jt1078.proc.response;
+
+import com.genersoft.iot.vmp.jt1078.annotation.MsgId;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
+
+/**
+ * 查询资源列表
+ *
+ * @author QingtaiJiang
+ * @date 2023/4/28 10:36
+ * @email qingtaij@163.com
+ */
+@MsgId(id = "9205")
+public class J9205 extends Rs {
+    // 逻辑通道号
+    private int channelId;
+
+    // 开始时间YYMMDDHHMMSS,全0表示无起始时间
+    private String startTime;
+
+    // 结束时间YYMMDDHHMMSS,全0表示无终止时间
+    private String endTime;
+
+    // 报警标志
+    private final int warnType = 0;
+
+    // 音视频资源类型:0.音视频 1.音频 2.视频 3.视频或音视频
+    private int mediaType;
+
+    // 码流类型:0.所有码流 1.主码流 2.子码流
+    private int streamType = 0;
+
+    // 存储器类型:0.所有存储器 1.主存储器 2.灾备存储器
+    private int storageType = 0;
+
+    @Override
+    public ByteBuf encode() {
+        ByteBuf buffer = Unpooled.buffer();
+
+        buffer.writeByte(channelId);
+        buffer.writeBytes(ByteBufUtil.decodeHexDump(startTime));
+        buffer.writeBytes(ByteBufUtil.decodeHexDump(endTime));
+        buffer.writeLong(warnType);
+        buffer.writeByte(mediaType);
+        buffer.writeByte(streamType);
+        buffer.writeByte(storageType);
+
+        return buffer;
+    }
+
+
+    public void setChannelId(int channelId) {
+        this.channelId = channelId;
+    }
+
+    public void setStartTime(String startTime) {
+        this.startTime = startTime;
+    }
+
+    public void setEndTime(String endTime) {
+        this.endTime = endTime;
+    }
+
+    public void setMediaType(int mediaType) {
+        this.mediaType = mediaType;
+    }
+
+    public void setStreamType(int streamType) {
+        this.streamType = streamType;
+    }
+
+    public void setStorageType(int storageType) {
+        this.storageType = storageType;
+    }
+
+    public int getWarnType() {
+        return warnType;
+    }
+
+    @Override
+    public String toString() {
+        return "J9205{" +
+                "channelId=" + channelId +
+                ", startTime='" + startTime + '\'' +
+                ", endTime='" + endTime + '\'' +
+                ", warnType=" + warnType +
+                ", mediaType=" + mediaType +
+                ", streamType=" + streamType +
+                ", storageType=" + storageType +
+                '}';
+    }
+}

+ 27 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/Rs.java

@@ -0,0 +1,27 @@
+package com.genersoft.iot.vmp.jt1078.proc.response;
+
+
+import com.genersoft.iot.vmp.jt1078.proc.Header;
+import io.netty.buffer.ByteBuf;
+
+
+/**
+ * @author QingtaiJiang
+ * @date 2021/8/30 18:54
+ * @email qingtaij@163.com
+ */
+
+public abstract class Rs {
+    private Header header;
+
+    public abstract ByteBuf encode();
+
+
+    public Header getHeader() {
+        return header;
+    }
+
+    public void setHeader(Header header) {
+        this.header = header;
+    }
+}

+ 114 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/session/Session.java

@@ -0,0 +1,114 @@
+package com.genersoft.iot.vmp.jt1078.session;
+
+import com.genersoft.iot.vmp.jt1078.proc.Header;
+import io.netty.channel.Channel;
+import io.netty.util.AttributeKey;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * @author QingtaiJiang
+ * @date 2023/4/27 18:54
+ * @email qingtaij@163.com
+ */
+public class Session {
+    private final static Logger log = LoggerFactory.getLogger(Session.class);
+
+    public static final AttributeKey<Session> KEY = AttributeKey.newInstance(Session.class.getName());
+
+    // Netty的channel
+    protected final Channel channel;
+
+    // 原子类的自增ID
+    private final AtomicInteger serialNo = new AtomicInteger(0);
+
+    // 是否注册成功
+    private boolean registered = false;
+
+    // 设备ID
+    private String devId;
+
+    // 创建时间
+    private final long creationTime;
+
+    // 协议版本号
+    private Integer protocolVersion;
+
+    private Header header;
+
+    protected Session(Channel channel) {
+        this.channel = channel;
+        this.creationTime = System.currentTimeMillis();
+    }
+
+    public void writeObject(Object message) {
+        log.info("<<<<<<<<<< cmd{},{}", this, message);
+        channel.writeAndFlush(message);
+    }
+
+    /**
+     * 获得下一个流水号
+     *
+     * @return 流水号
+     */
+    public int nextSerialNo() {
+        int current;
+        int next;
+        do {
+            current = serialNo.get();
+            next = current > 0xffff ? 0 : current;
+        } while (!serialNo.compareAndSet(current, next + 1));
+        return next;
+    }
+
+    /**
+     * 注册session
+     *
+     * @param devId 设备ID
+     */
+    public void register(String devId, Integer version, Header header) {
+        this.devId = devId;
+        this.registered = true;
+        this.protocolVersion = version;
+        this.header = header;
+        SessionManager.INSTANCE.put(devId, this);
+    }
+
+    /**
+     * 获取设备号
+     *
+     * @return 设备号
+     */
+    public String getDevId() {
+        return devId;
+    }
+
+
+    public boolean isRegistered() {
+        return registered;
+    }
+
+    public long getCreationTime() {
+        return creationTime;
+    }
+
+    public Integer getProtocolVersion() {
+        return protocolVersion;
+    }
+
+    public Header getHeader() {
+        return header;
+    }
+
+    @Override
+    public String toString() {
+        return "[" +
+                "devId=" + devId +
+                ", reg=" + registered +
+                ", version=" + protocolVersion +
+                ",ip=" + channel.remoteAddress() +
+                ']';
+    }
+}

+ 127 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/session/SessionManager.java

@@ -0,0 +1,127 @@
+package com.genersoft.iot.vmp.jt1078.session;
+
+import com.genersoft.iot.vmp.jt1078.proc.entity.Cmd;
+import io.netty.channel.Channel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * @author QingtaiJiang
+ * @date 2023/4/27 19:54
+ * @email qingtaij@163.com
+ */
+public enum SessionManager {
+    INSTANCE;
+    private final static Logger log = LoggerFactory.getLogger(SessionManager.class);
+
+    // 用与消息的缓存
+    private final Map<String, SynchronousQueue<String>> topicSubscribers = new ConcurrentHashMap<>();
+
+    // session的缓存
+    private final Map<Object, Session> sessionMap;
+
+    SessionManager() {
+        this.sessionMap = new ConcurrentHashMap<>();
+    }
+
+    /**
+     * 创建新的Session
+     *
+     * @param channel netty通道
+     * @return 创建的session对象
+     */
+    public Session newSession(Channel channel) {
+        return new Session(channel);
+    }
+
+
+    /**
+     * 获取指定设备的Session
+     *
+     * @param clientId 设备Id
+     * @return Session
+     */
+    public Session get(Object clientId) {
+        return sessionMap.get(clientId);
+    }
+
+    /**
+     * 放入新设备连接的session
+     *
+     * @param clientId   设备ID
+     * @param newSession session
+     */
+    protected void put(Object clientId, Session newSession) {
+        sessionMap.put(clientId, newSession);
+    }
+
+
+    /**
+     * 发送同步消息,接收响应
+     * 默认超时时间6秒
+     */
+    public String request(Cmd cmd) {
+        // 默认6秒
+        int timeOut = 6000;
+        return request(cmd, timeOut);
+    }
+
+    public String request(Cmd cmd, Integer timeOut) {
+        Session session = this.get(cmd.getDevId());
+        if (session == null) {
+            log.error("DevId: {} not online!", cmd.getDevId());
+            return null;
+        }
+        String requestKey = requestKey(cmd.getDevId(), cmd.getRespId(), cmd.getPackageNo());
+        SynchronousQueue<String> subscribe = subscribe(requestKey);
+        if (subscribe == null) {
+            log.error("DevId: {} key:{} send repaid", cmd.getDevId(), requestKey);
+            return null;
+        }
+        session.writeObject(cmd);
+        try {
+            return subscribe.poll(timeOut, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            log.warn("<<<<<<<<<< timeout" + session, e);
+        } finally {
+            this.unsubscribe(requestKey);
+        }
+        return null;
+    }
+
+    public Boolean response(String devId, String respId, Long responseNo, String data) {
+        String requestKey = requestKey(devId, respId, responseNo);
+        SynchronousQueue<String> queue = topicSubscribers.get(requestKey);
+        if (queue != null) {
+            try {
+                return queue.offer(data, 2, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                log.error("{}", e.getMessage(), e);
+            }
+        }
+        log.warn("Not find response,key:{} data:{} ", requestKey, data);
+        return false;
+    }
+
+    private void unsubscribe(String key) {
+        topicSubscribers.remove(key);
+    }
+
+    private SynchronousQueue<String> subscribe(String key) {
+        SynchronousQueue<String> queue = null;
+        if (!topicSubscribers.containsKey(key))
+            topicSubscribers.put(key, queue = new SynchronousQueue<String>());
+        return queue;
+    }
+
+    private String requestKey(String devId, String respId, Long requestNo) {
+        return String.join("_", devId.replaceFirst("^0*", ""), respId, requestNo.toString());
+    }
+
+}

+ 41 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/util/Bin.java

@@ -0,0 +1,41 @@
+package com.genersoft.iot.vmp.jt1078.util;
+
+/**
+ * 32位整型的二进制读写
+ */
+public class Bin {
+
+    private static final int[] bits = new int[32];
+
+    static {
+        bits[0] = 1;
+        for (int i = 1; i < bits.length; i++) {
+            bits[i] = bits[i - 1] << 1;
+        }
+    }
+
+    /**
+     * 读取n的第i位
+     *
+     * @param n int32
+     * @param i 取值范围0-31
+     */
+    public static boolean get(int n, int i) {
+        return (n & bits[i]) == bits[i];
+    }
+
+    /**
+     * 不足位数从左边加0
+     */
+    public static String strHexPaddingLeft(String data, int length) {
+        int dataLength = data.length();
+        if (dataLength < length) {
+            StringBuilder dataBuilder = new StringBuilder(data);
+            for (int i = dataLength; i < length; i++) {
+                dataBuilder.insert(0, "0");
+            }
+            data = dataBuilder.toString();
+        }
+        return data;
+    }
+}

+ 112 - 0
src/main/java/com/genersoft/iot/vmp/jt1078/util/ClassUtil.java

@@ -0,0 +1,112 @@
+package com.genersoft.iot.vmp.jt1078.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+
+import java.lang.annotation.Annotation;
+import java.util.LinkedList;
+import java.util.List;
+
+public class ClassUtil {
+
+    private static final Logger logger = LoggerFactory.getLogger(ClassUtil.class);
+
+
+    public static Object getBean(Class<?> clazz) {
+        if (clazz != null) {
+            try {
+                return clazz.getDeclaredConstructor().newInstance();
+            } catch (Exception ex) {
+                logger.error("ClassUtil:找不到指定的类", ex);
+            }
+        }
+        return null;
+    }
+
+
+    public static Object getBean(String className) {
+        Class<?> clazz = null;
+        try {
+            clazz = Class.forName(className);
+        } catch (Exception ex) {
+            logger.error("ClassUtil:找不到指定的类");
+        }
+        if (clazz != null) {
+            try {
+                //获取声明的构造器--》创建实例
+                return clazz.getDeclaredConstructor().newInstance();
+            } catch (Exception ex) {
+                logger.error("ClassUtil:找不到指定的类", ex);
+            }
+        }
+        return null;
+    }
+
+
+    /**
+     * 获取包下所有带注解的class
+     *
+     * @param packageName     包名
+     * @param annotationClass 注解类型
+     * @return list
+     */
+    public static List<Class<?>> getClassList(String packageName, Class<? extends Annotation> annotationClass) {
+        List<Class<?>> classList = getClassList(packageName);
+        classList.removeIf(next -> !next.isAnnotationPresent(annotationClass));
+        return classList;
+    }
+
+    public static List<Class<?>> getClassList(String... packageName) {
+        List<Class<?>> classList = new LinkedList<>();
+        for (String s : packageName) {
+            List<Class<?>> c = getClassList(s);
+            classList.addAll(c);
+        }
+        return classList;
+    }
+
+    public static List<Class<?>> getClassList(String packageName) {
+        List<Class<?>> classList = new LinkedList<>();
+        try {
+            ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
+            Resource[] resources = resourcePatternResolver.getResources(packageName.replace(".", "/") + "/**/*.class");
+            for (Resource resource : resources) {
+                String url = resource.getURL().toString();
+
+                String[] split = url.split(packageName.replace(".", "/"));
+                String s = split[split.length - 1];
+                String className = s.replace("/", ".");
+                className = className.substring(0, className.lastIndexOf("."));
+                doAddClass(classList, packageName + className);
+            }
+
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return classList;
+    }
+
+    private static void doAddClass(List<Class<?>> classList, String className) {
+        Class<?> cls = loadClass(className, false);
+        classList.add(cls);
+    }
+
+    public static Class<?> loadClass(String className, boolean isInitialized) {
+        Class<?> cls;
+        try {
+            cls = Class.forName(className, isInitialized, getClassLoader());
+        } catch (ClassNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+        return cls;
+    }
+
+
+    public static ClassLoader getClassLoader() {
+        return Thread.currentThread().getContextClassLoader();
+    }
+
+}

+ 208 - 226
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java

@@ -2,12 +2,16 @@ package com.genersoft.iot.vmp.media.zlm;
 
 import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONObject;
+import com.genersoft.iot.vmp.common.InviteInfo;
+import com.genersoft.iot.vmp.common.InviteSessionType;
 import com.genersoft.iot.vmp.common.StreamInfo;
 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.EventPublisher;
 import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
+import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
+import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
@@ -21,10 +25,8 @@ import com.genersoft.iot.vmp.media.zlm.dto.hook.*;
 import com.genersoft.iot.vmp.service.*;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
-import com.genersoft.iot.vmp.vmanager.bean.DeferredResultEx;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
 import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
-import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -57,14 +59,14 @@ public class ZLMHttpHookListener {
     @Autowired
     private SIPCommander cmder;
 
-	@Autowired
-	private ISIPCommanderForPlatform commanderFroPlatform;
+    @Autowired
+    private ISIPCommanderForPlatform commanderFroPlatform;
 
-	@Autowired
-	private AudioBroadcastManager audioBroadcastManager;
+    @Autowired
+    private AudioBroadcastManager audioBroadcastManager;
 
-	@Autowired
-	private ZLMRTPServerFactory zlmrtpServerFactory;
+    @Autowired
+    private ZLMRTPServerFactory zlmrtpServerFactory;
 
     @Autowired
     private IPlayService playService;
@@ -75,6 +77,9 @@ public class ZLMHttpHookListener {
     @Autowired
     private IRedisCatchStorage redisCatchStorage;
 
+    @Autowired
+    private IInviteStreamService inviteStreamService;
+
     @Autowired
     private IDeviceService deviceService;
 
@@ -111,6 +116,9 @@ public class ZLMHttpHookListener {
     @Autowired
     private AssistRESTfulUtils assistRESTfulUtils;
 
+    @Autowired
+    private SSRCFactory ssrcFactory;
+
     @Qualifier("taskExecutor")
     @Autowired
     private ThreadPoolTaskExecutor taskExecutor;
@@ -255,13 +263,13 @@ public class ZLMHttpHookListener {
                 result.setEnable_audio(deviceChannel.isHasAudio());
             }
             // 如果是录像下载就设置视频间隔十秒
-            if (ssrcTransactionForAll.get(0).getType() == VideoStreamSessionManager.SessionType.download) {
+            if (ssrcTransactionForAll.get(0).getType() == InviteSessionType.DOWNLOAD) {
                 result.setMp4_max_second(10);
                 result.setEnable_audio(true);
                 result.setEnable_mp4(true);
             }
             // 如果是talk对讲,则默认获取声音
-            if (ssrcTransactionForAll.get(0).getType() == VideoStreamSessionManager.SessionType.talk) {
+            if (ssrcTransactionForAll.get(0).getType() == InviteSessionType.TALK) {
                 result.setEnable_audio(true);
             }
 
@@ -269,7 +277,7 @@ public class ZLMHttpHookListener {
         if (mediaInfo.getRecordAssistPort() > 0 && userSetting.getRecordPath() == null) {
             logger.info("推流时发现尚未设置录像路径,从assist服务中读取");
             JSONObject info = assistRESTfulUtils.getInfo(mediaInfo, null);
-            if (info != null && info.getInteger("code") != null && info.getInteger("code") == 0 ) {
+            if (info != null && info.getInteger("code") != null && info.getInteger("code") == 0) {
                 JSONObject dataJson = info.getJSONObject("data");
                 if (dataJson != null) {
                     String recordPath = dataJson.getString("record");
@@ -301,29 +309,30 @@ public class ZLMHttpHookListener {
             logger.info("[ZLM HOOK] 流注销, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
         }
 
-		JSONObject ret = new JSONObject();
-		ret.put("code", 0);
-		ret.put("msg", "success");
+        JSONObject ret = new JSONObject();
+        ret.put("code", 0);
+        ret.put("msg", "success");
         MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId());
         JSONObject json = (JSONObject) JSON.toJSON(param);
         taskExecutor.execute(() -> {
             ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_stream_changed, json);
+            if (mediaInfo == null) {
+                logger.info("[ZLM HOOK] 流变化未找到ZLM, {}", param.getMediaServerId());
+                return;
+            }
             if (subscribe != null) {
-
-                if (mediaInfo != null) {
-                    subscribe.response(mediaInfo, json);
-                }
+                subscribe.response(mediaInfo, json);
             }
 
             List<OnStreamChangedHookParam.MediaTrack> tracks = param.getTracks();
             // TODO 重构此处逻辑
-
+            boolean isPush = false;
             if (param.isRegist()) {
                 // 处理流注册的鉴权信息
                 if (param.getOriginType() == OriginType.RTMP_PUSH.ordinal()
                         || param.getOriginType() == OriginType.RTSP_PUSH.ordinal()
                         || param.getOriginType() == OriginType.RTC_PUSH.ordinal()) {
-
+                    isPush = true;
                     StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream());
                     if (streamAuthorityInfo == null) {
                         streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param);
@@ -337,123 +346,122 @@ public class ZLMHttpHookListener {
                 redisCatchStorage.removeStreamAuthorityInfo(param.getApp(), param.getStream());
             }
 
-		if ("rtsp".equals(param.getSchema())){
-			logger.info("流变化:注册->{}, app->{}, stream->{}", param.isRegist(), param.getApp(), param.getStream());
-			if (param.isRegist()) {
-				mediaServerService.addCount(param.getMediaServerId());
-			}else {
-				mediaServerService.removeCount(param.getMediaServerId());
-			}
-			if (param.getOriginType() == OriginType.PULL.ordinal()
-					|| param.getOriginType() == OriginType.FFMPEG_PULL.ordinal()) {
-				// 设置拉流代理上线/离线
-				streamProxyService.updateStatus(param.isRegist(), param.getApp(), param.getStream());
-			}
-			if ("rtp".equals(param.getApp()) && !param.isRegist() ) {
-				StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(param.getStream());
-				if (streamInfo!=null){
-					redisCatchStorage.stopPlay(streamInfo);
-					storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
-				}else{
-					streamInfo = redisCatchStorage.queryPlayback(null, null, param.getStream(), null);
-					if (streamInfo != null) {
-						redisCatchStorage.stopPlayback(streamInfo.getDeviceID(), streamInfo.getChannelId(),
-								streamInfo.getStream(), null);
-					}
-				}
-			}else if ("broadcast".equals(param.getApp())){
-				// 语音对讲推流  stream需要满足格式deviceId_channelId
-                if (param.getStream().indexOf("_") > 0) {
-                    String[] streamArray = param.getStream().split("_");
-                    if (streamArray.length == 2) {
-                        String deviceId = streamArray[0];
-                        String channelId = streamArray[1];
-                        Device device = deviceService.getDevice(deviceId);
-                        if (device != null) {
-                            if (param.isRegist()) {
-                                if (audioBroadcastManager.exit(deviceId, channelId)) {
+            if ("rtsp".equals(param.getSchema())) {
+                logger.info("流变化:注册->{}, app->{}, stream->{}", param.isRegist(), param.getApp(), param.getStream());
+                if (param.isRegist()) {
+                    mediaServerService.addCount(param.getMediaServerId());
+                } else {
+                    mediaServerService.removeCount(param.getMediaServerId());
+                }
+
+                int updateStatusResult = streamProxyService.updateStatus(param.isRegist(), param.getApp(), param.getStream());
+                if (updateStatusResult > 0) {
+
+                }
+
+                if ("rtp".equals(param.getApp()) && !param.isRegist()) {
+                    InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, param.getStream());
+                    if (inviteInfo != null && (inviteInfo.getType() == InviteSessionType.PLAY || inviteInfo.getType() == InviteSessionType.PLAYBACK)) {
+                        inviteStreamService.removeInviteInfo(inviteInfo);
+                        storager.stopPlay(inviteInfo.getDeviceId(), inviteInfo.getChannelId());
+                    }
+                } else if ("broadcast".equals(param.getApp())) {
+                    // 语音对讲推流  stream需要满足格式deviceId_channelId
+                    if (param.getStream().indexOf("_") > 0) {
+                        String[] streamArray = param.getStream().split("_");
+                        if (streamArray.length == 2) {
+                            String deviceId = streamArray[0];
+                            String channelId = streamArray[1];
+                            Device device = deviceService.getDevice(deviceId);
+                            if (device != null) {
+                                if (param.isRegist()) {
+                                    if (audioBroadcastManager.exit(deviceId, channelId)) {
+                                        playService.stopAudioBroadcast(deviceId, channelId);
+                                    }
+                                    // 开启语音对讲通道
+                                    try {
+                                        playService.audioBroadcastCmd(device, channelId, mediaInfo, param.getApp(), param.getStream(), 60, false, (msg) -> {
+                                            logger.info("[语音对讲] 通道建立成功, device: {}, channel: {}", deviceId, channelId);
+                                        });
+                                    } catch (InvalidArgumentException | ParseException | SipException e) {
+                                        logger.error("[命令发送失败] 语音对讲: {}", e.getMessage());
+                                    }
+                                } else {
+                                    // 流注销
                                     playService.stopAudioBroadcast(deviceId, channelId);
                                 }
-                                // 开启语音对讲通道
-                                try {
-                                    playService.audioBroadcastCmd(device, channelId, mediaInfo, param.getApp(), param.getStream(), 60, false, (msg)->{
-                                        logger.info("[语音对讲] 通道建立成功, device: {}, channel: {}", deviceId, channelId);
-                                    });
-                                } catch (InvalidArgumentException | ParseException | SipException e) {
-                                    logger.error("[命令发送失败] 语音对讲: {}", e.getMessage());
-                                }
-                            }else {
-                                // 流注销
-                                playService.stopAudioBroadcast(deviceId, channelId);
+                            } else {
+                                logger.info("[语音对讲] 未找到设备:{}", deviceId);
                             }
-                        } else{
-                            logger.info("[语音对讲] 未找到设备:{}", deviceId);
                         }
                     }
-                }
-			}else if ("talk".equals(param.getApp())){
-				// 语音对讲推流  stream需要满足格式deviceId_channelId
-                if (param.getStream().indexOf("_") > 0) {
-                    String[] streamArray = param.getStream().split("_");
-                    if (streamArray.length == 2) {
-                        String deviceId = streamArray[0];
-                        String channelId = streamArray[1];
-                        Device device = deviceService.getDevice(deviceId);
-                        if (device != null) {
-                            if (param.isRegist()) {
-                                if (audioBroadcastManager.exit(deviceId, channelId)) {
-                                    playService.stopAudioBroadcast(deviceId, channelId);
+                } else if ("talk".equals(param.getApp())) {
+                    // 语音对讲推流  stream需要满足格式deviceId_channelId
+                    if (param.getStream().indexOf("_") > 0) {
+                        String[] streamArray = param.getStream().split("_");
+                        if (streamArray.length == 2) {
+                            String deviceId = streamArray[0];
+                            String channelId = streamArray[1];
+                            Device device = deviceService.getDevice(deviceId);
+                            if (device != null) {
+                                if (param.isRegist()) {
+                                    if (audioBroadcastManager.exit(deviceId, channelId)) {
+                                        playService.stopAudioBroadcast(deviceId, channelId);
+                                    }
+                                    // 开启语音对讲通道
+                                    playService.talkCmd(device, channelId, mediaInfo, param.getStream(), (msg) -> {
+                                        logger.info("[语音对讲] 通道建立成功, device: {}, channel: {}", deviceId, channelId);
+                                    });
+                                } else {
+                                    // 流注销
+                                    playService.stopTalk(device, channelId, param.isRegist());
                                 }
-                                // 开启语音对讲通道
-                                playService.talkCmd(device, channelId, mediaInfo, param.getStream(), (msg)->{
-                                    logger.info("[语音对讲] 通道建立成功, device: {}, channel: {}", deviceId, channelId);
-                                });
-                            }else {
-                                // 流注销
-                                playService.stopTalk(device, channelId, param.isRegist());
+                            } else {
+                                logger.info("[语音对讲] 未找到设备:{}", deviceId);
                             }
-                        } else{
-                            logger.info("[语音对讲] 未找到设备:{}", deviceId);
                         }
                     }
-                }
 
-			}else{
-				if (!"rtp".equals(param.getApp())){
-					String type = OriginType.values()[param.getOriginType()].getType();
-					MediaServerItem mediaServerItem = mediaServerService.getOne(param.getMediaServerId());
-
-						if (mediaServerItem != null){
-							if (param.isRegist()) {
-								StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream());
-								String callId = null;
-								if (streamAuthorityInfo != null) {
-									callId = streamAuthorityInfo.getCallId();
-								}
-								StreamInfo streamInfoByAppAndStream = mediaService.getStreamInfoByAppAndStream(mediaServerItem,
-										param.getApp(), param.getStream(), param.getTracks(), callId);
-								param.setStreamInfo(new StreamContent(streamInfoByAppAndStream));
-								redisCatchStorage.addStream(mediaServerItem, type, param.getApp(), param.getStream(), param);
-								if (param.getOriginType() == OriginType.RTSP_PUSH.ordinal()
-										|| param.getOriginType() == OriginType.RTMP_PUSH.ordinal()
-										|| param.getOriginType() == OriginType.RTC_PUSH.ordinal() ) {
-									param.setSeverId(userSetting.getServerId());
-									zlmMediaListManager.addPush(param);
-								}
-							}else {
-								// 兼容流注销时类型从redis记录获取
-								OnStreamChangedHookParam onStreamChangedHookParam = redisCatchStorage.getStreamInfo(
-										param.getApp(), param.getStream(), param.getMediaServerId());
-								if (onStreamChangedHookParam != null) {
-									type = OriginType.values()[onStreamChangedHookParam.getOriginType()].getType();
-									redisCatchStorage.removeStream(mediaServerItem.getId(), type, param.getApp(), param.getStream());
-								}
-								GbStream gbStream = storager.getGbStream(param.getApp(), param.getStream());
-								if (gbStream != null) {
+                } else {
+                    if (!"rtp".equals(param.getApp())) {
+                        String type = OriginType.values()[param.getOriginType()].getType();
+                        MediaServerItem mediaServerItem = mediaServerService.getOne(param.getMediaServerId());
+
+                        if (mediaServerItem != null) {
+                            if (param.isRegist()) {
+                                StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream());
+                                String callId = null;
+                                if (streamAuthorityInfo != null) {
+                                    callId = streamAuthorityInfo.getCallId();
+                                }
+                                StreamInfo streamInfoByAppAndStream = mediaService.getStreamInfoByAppAndStream(mediaServerItem,
+                                        param.getApp(), param.getStream(), param.getTracks(), callId);
+                                param.setStreamInfo(new StreamContent(streamInfoByAppAndStream));
+                                redisCatchStorage.addStream(mediaServerItem, type, param.getApp(), param.getStream(), param);
+                                if (param.getOriginType() == OriginType.RTSP_PUSH.ordinal()
+                                        || param.getOriginType() == OriginType.RTMP_PUSH.ordinal()
+                                        || param.getOriginType() == OriginType.RTC_PUSH.ordinal()) {
+                                    param.setSeverId(userSetting.getServerId());
+                                    zlmMediaListManager.addPush(param);
+                                }
+                            } else {
+                                // 兼容流注销时类型从redis记录获取
+                                OnStreamChangedHookParam onStreamChangedHookParam = redisCatchStorage.getStreamInfo(
+                                        param.getApp(), param.getStream(), param.getMediaServerId());
+                                if (onStreamChangedHookParam != null) {
+                                    type = OriginType.values()[onStreamChangedHookParam.getOriginType()].getType();
+                                    redisCatchStorage.removeStream(mediaServerItem.getId(), type, param.getApp(), param.getStream());
+                                }
+                                GbStream gbStream = storager.getGbStream(param.getApp(), param.getStream());
+                                if (gbStream != null) {
 //									eventPublisher.catalogEventPublishForStream(null, gbStream, CatalogEvent.OFF);
                                 }
                                 zlmMediaListManager.removeMedia(param.getApp(), param.getStream());
                             }
+                            GbStream gbStream = storager.getGbStream(param.getApp(), param.getStream());
+                            if (gbStream != null) {
+                                eventPublisher.catalogEventPublishForStream(null, gbStream, param.isRegist() ? CatalogEvent.ON : CatalogEvent.OFF);
+                            }
                             if (type != null) {
                                 // 发送流变化redis消息
                                 JSONObject jsonObject = new JSONObject();
@@ -466,38 +474,35 @@ public class ZLMHttpHookListener {
                             }
                         }
                     }
-                }
-                if (!param.isRegist()) {
-                    List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByStream(param.getStream());
-                    if (sendRtpItems.size() > 0) {
-                        for (SendRtpItem sendRtpItem : sendRtpItems) {
-                            if (sendRtpItem.getApp().equals(param.getApp())) {
-                                String platformId = sendRtpItem.getPlatformId();
-                                ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId);
-                                Device device = deviceService.getDevice(platformId);
-
-
-                                    if (platform != null) {
-                                        try {
+                    if (!param.isRegist()) {
+                        List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByStream(param.getStream());
+                        if (sendRtpItems.size() > 0) {
+                            for (SendRtpItem sendRtpItem : sendRtpItems) {
+                                if (sendRtpItem != null && sendRtpItem.getApp().equals(param.getApp())) {
+                                    String platformId = sendRtpItem.getPlatformId();
+                                    ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId);
+                                    Device device = deviceService.getDevice(platformId);
+
+                                    try {
+                                        if (platform != null) {
                                             commanderFroPlatform.streamByeCmd(platform, sendRtpItem);
-                                        } catch (SipException | InvalidArgumentException | ParseException e) {
-                                            logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
-                                        }
-                                    } else {
-                                        try {
+                                            redisCatchStorage.deleteSendRTPServer(platformId, sendRtpItem.getChannelId(),
+                                                    sendRtpItem.getCallId(), sendRtpItem.getStream());
+                                        } else {
                                             cmder.streamByeCmd(device, sendRtpItem.getChannelId(), param.getStream(), sendRtpItem.getCallId());
-                                        } catch (SipException | InvalidArgumentException | ParseException |
-                                                 SsrcTransactionNotFoundException e) {
-                                            logger.error("[命令发送失败] 发送BYE: {}", e.getMessage());
                                         }
+                                    } catch (SipException | InvalidArgumentException | ParseException |
+                                             SsrcTransactionNotFoundException e) {
+                                        logger.error("[命令发送失败] 发送BYE: {}", e.getMessage());
                                     }
+                                }
                             }
                         }
                     }
                 }
+
             }
         });
-
         return HookResult.SUCCESS();
     }
 
@@ -508,82 +513,65 @@ public class ZLMHttpHookListener {
     @PostMapping(value = "/on_stream_none_reader", produces = "application/json;charset=UTF-8")
     public JSONObject onStreamNoneReader(@RequestBody OnStreamNoneReaderHookParam param) {
 
-		logger.info("[ZLM HOOK]流无人观看:{]->{}->{}/{}" + param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
-		JSONObject ret = new JSONObject();
-		ret.put("code", 0);
-		// 国标类型的流
-		if ("rtp".equals(param.getApp())){
-			ret.put("close", userSetting.getStreamOnDemand());
-			// 国标流, 点播/录像回放/录像下载
-			StreamInfo streamInfoForPlayCatch = redisCatchStorage.queryPlayByStreamId(param.getStream());
-			// 点播
-			if (streamInfoForPlayCatch != null) {
-				// 收到无人观看说明流也没有在往上级推送
-				if (redisCatchStorage.isChannelSendingRTP(streamInfoForPlayCatch.getChannelId())) {
-					List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByChnnelId(streamInfoForPlayCatch.getChannelId());
-					if (sendRtpItems.size() > 0) {
-						for (SendRtpItem sendRtpItem : sendRtpItems) {
-							ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
-							if (parentPlatform == null) {
-								continue;
-							}
-							try {
-								commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId());
-							} catch (SipException | InvalidArgumentException | ParseException e) {
-								logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
-							}
-							redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(),
-									sendRtpItem.getCallId(), sendRtpItem.getStream());
-						}
-					}
-				}
-				Device device = deviceService.getDevice(streamInfoForPlayCatch.getDeviceID());
-				if (device != null) {
-					try {
-						cmder.streamByeCmd(device, streamInfoForPlayCatch.getChannelId(),
-								streamInfoForPlayCatch.getStream(), null);
-					} catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) {
-						logger.error("[无人观看]点播, 发送BYE失败 {}", e.getMessage());
-					}
-				}
-
-                redisCatchStorage.stopPlay(streamInfoForPlayCatch);
-                storager.stopPlay(streamInfoForPlayCatch.getDeviceID(), streamInfoForPlayCatch.getChannelId());
-                return ret;
-            }
-            // 录像回放
-            StreamInfo streamInfoForPlayBackCatch = redisCatchStorage.queryPlayback(null, null, param.getStream(), null);
-            if (streamInfoForPlayBackCatch != null) {
-                if (streamInfoForPlayBackCatch.isPause()) {
+        logger.info("[ZLM HOOK]流无人观看:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(),
+                param.getApp(), param.getStream());
+        JSONObject ret = new JSONObject();
+        ret.put("code", 0);
+        // 国标类型的流
+        if ("rtp".equals(param.getApp())) {
+            ret.put("close", userSetting.getStreamOnDemand());
+            // 国标流, 点播/录像回放/录像下载
+//            StreamInfo streamInfoForPlayCatch = redisCatchStorage.queryPlayByStreamId(param.getStream());
+
+            InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, param.getStream());
+            // 点播
+            if (inviteInfo != null) {
+                // 录像下载
+                if (inviteInfo.getType() == InviteSessionType.DOWNLOAD) {
                     ret.put("close", false);
-                } else {
-                    Device device = deviceService.getDevice(streamInfoForPlayBackCatch.getDeviceID());
-                    if (device != null) {
-                        try {
-                            cmder.streamByeCmd(device, streamInfoForPlayBackCatch.getChannelId(),
-                                    streamInfoForPlayBackCatch.getStream(), null);
-                        } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) {
-                            logger.error("[无人观看]回放, 发送BYE失败 {}", e.getMessage());
+                    return ret;
+                }
+                // 收到无人观看说明流也没有在往上级推送
+                if (redisCatchStorage.isChannelSendingRTP(inviteInfo.getChannelId())) {
+                    List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByChnnelId(
+                            inviteInfo.getChannelId());
+                    if (sendRtpItems.size() > 0) {
+                        for (SendRtpItem sendRtpItem : sendRtpItems) {
+                            ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
+                            try {
+                                commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId());
+                            } catch (SipException | InvalidArgumentException | ParseException e) {
+                                logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
+                            }
+                            redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(),
+                                    sendRtpItem.getCallId(), sendRtpItem.getStream());
                         }
                     }
-                    redisCatchStorage.stopPlayback(streamInfoForPlayBackCatch.getDeviceID(),
-                            streamInfoForPlayBackCatch.getChannelId(), streamInfoForPlayBackCatch.getStream(), null);
                 }
-                return ret;
-            }
-            // 录像下载
-            StreamInfo streamInfoForDownload = redisCatchStorage.queryDownload(null, null, param.getStream(), null);
-            // 进行录像下载时无人观看不断流
-            if (streamInfoForDownload != null) {
-                ret.put("close", false);
+                Device device = deviceService.getDevice(inviteInfo.getDeviceId());
+                if (device != null) {
+                    try {
+                        if (inviteStreamService.getInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId(), inviteInfo.getChannelId(), inviteInfo.getStream()) != null) {
+                            cmder.streamByeCmd(device, inviteInfo.getChannelId(),
+                                    inviteInfo.getStream(), null);
+                        }
+                    } catch (InvalidArgumentException | ParseException | SipException |
+                             SsrcTransactionNotFoundException e) {
+                        logger.error("[无人观看]点播, 发送BYE失败 {}", e.getMessage());
+                    }
+                }
+
+                inviteStreamService.removeInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId(),
+                        inviteInfo.getChannelId(), inviteInfo.getStream());
+                storager.stopPlay(inviteInfo.getDeviceId(), inviteInfo.getChannelId());
                 return ret;
             }
             SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, param.getStream(), null);
-            if ("talk".equals(sendRtpItem.getApp())){
+            if ("talk".equals(sendRtpItem.getApp())) {
                 ret.put("close", false);
                 return ret;
             }
-        }else if ("talk".equals(param.getApp()) || "broadcast".equals(param.getApp())){
+        } else if ("talk".equals(param.getApp()) || "broadcast".equals(param.getApp())) {
             ret.put("close", false);
         } else {
             // 非国标流 推流/拉流代理
@@ -652,6 +640,7 @@ public class ZLMHttpHookListener {
                 return defaultResult;
             }
             logger.info("[ZLM HOOK] 流未找到, 发起自动点播:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
+
             RequestMessage msg = new RequestMessage();
             String key = DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId;
             boolean exist = resultHolder.exist(key, null);
@@ -659,31 +648,22 @@ public class ZLMHttpHookListener {
             String uuid = UUID.randomUUID().toString();
             msg.setId(uuid);
             DeferredResult<HookResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue());
-            DeferredResultEx<HookResult> deferredResultEx = new DeferredResultEx<>(result);
 
             result.onTimeout(() -> {
-                logger.info("点播接口等待超时");
+                logger.info("[ZLM HOOK] 自动点播, 等待超时");
                 // 释放rtpserver
                 msg.setData(new HookResult(ErrorCode.ERROR100.getCode(), "点播超时"));
                 resultHolder.invokeResult(msg);
             });
-            // TODO 在点播未成功的情况下在此调用接口点播会导致返回的流地址ip错误
-            deferredResultEx.setFilter(result1 -> {
-                WVPResult<StreamInfo> wvpResult1 = (WVPResult<StreamInfo>) result1;
-                HookResult resultForEnd = new HookResult();
-                resultForEnd.setCode(wvpResult1.getCode());
-                resultForEnd.setMsg(wvpResult1.getMsg());
-                return resultForEnd;
-            });
 
             // 录像查询以channelId作为deviceId查询
-            resultHolder.put(key, uuid, deferredResultEx);
+            resultHolder.put(key, uuid, result);
 
             if (!exist) {
-                playService.play(mediaInfo, deviceId, channelId, null, eventResult -> {
-                    msg.setData(new HookResult(eventResult.statusCode, eventResult.msg));
+                playService.play(mediaInfo, deviceId, channelId, (code, message, data) -> {
+                    msg.setData(new HookResult(code, message));
                     resultHolder.invokeResult(msg);
-                }, null);
+                });
             }
             return result;
         } else {
@@ -740,6 +720,7 @@ public class ZLMHttpHookListener {
             if (sendRtpItems.size() > 0) {
                 for (SendRtpItem sendRtpItem : sendRtpItems) {
                     ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
+                    ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc());
                     try {
                         commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId());
                     } catch (SipException | InvalidArgumentException | ParseException e) {
@@ -759,7 +740,8 @@ public class ZLMHttpHookListener {
      */
     @ResponseBody
     @PostMapping(value = "/on_rtp_server_timeout", produces = "application/json;charset=UTF-8")
-    public HookResult onRtpServerTimeout(HttpServletRequest request, @RequestBody OnRtpServerTimeoutHookParam param) {
+    public HookResult onRtpServerTimeout(HttpServletRequest request, @RequestBody OnRtpServerTimeoutHookParam
+            param) {
         logger.info("[ZLM HOOK] rtpServer收流超时:{}->{}({})", param.getMediaServerId(), param.getStream_id(), param.getSsrc());
 
         taskExecutor.execute(() -> {

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

@@ -97,7 +97,8 @@ public class ZLMMediaListManager {
     public void sendStreamEvent(String app, String stream, String mediaServerId) {
         MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
         // 查看推流状态
-        if (zlmrtpServerFactory.isStreamReady(mediaServerItem, app, stream)) {
+        Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, app, stream);
+        if (streamReady != null && streamReady) {
             ChannelOnlineEvent channelOnlineEventLister = getChannelOnlineEventLister(app, stream);
             if (channelOnlineEventLister != null)  {
                 try {

+ 21 - 0
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java

@@ -25,6 +25,8 @@ public class ZLMRESTfulUtils {
 
     private OkHttpClient client;
 
+
+
     public interface RequestCallback{
         void run(JSONObject response);
     }
@@ -279,6 +281,10 @@ public class ZLMRESTfulUtils {
         return sendPost(mediaServerItem, "closeRtpServer",param, null);
     }
 
+    public void closeRtpServer(MediaServerItem mediaServerItem, Map<String, Object> param, RequestCallback callback) {
+        sendPost(mediaServerItem, "closeRtpServer",param, callback);
+    }
+
     public JSONObject listRtpServer(MediaServerItem mediaServerItem) {
         return sendPost(mediaServerItem, "listRtpServer",null, null);
     }
@@ -353,4 +359,19 @@ public class ZLMRESTfulUtils {
         param.put("stream_id", streamId);
         return sendPost(mediaServerItem, "resumeRtpCheck",param, null);
     }
+
+    public JSONObject connectRtpServer(MediaServerItem mediaServerItem, String dst_url, int dst_port, String stream_id) {
+        Map<String, Object> param = new HashMap<>(1);
+        param.put("dst_url", dst_url);
+        param.put("dst_port", dst_port);
+        param.put("stream_id", stream_id);
+        return sendPost(mediaServerItem, "connectRtpServer",param, null);
+    }
+
+    public JSONObject updateRtpServerSSRC(MediaServerItem mediaServerItem, String streamId, String ssrc) {
+        Map<String, Object> param = new HashMap<>(1);
+        param.put("ssrc", ssrc);
+        param.put("stream_id", streamId);
+        return sendPost(mediaServerItem, "updateRtpServerSSRC",param, null);
+    }
 }

+ 73 - 8
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java

@@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.media.zlm;
 import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONArray;
 import com.alibaba.fastjson2.JSONObject;
+import com.genersoft.iot.vmp.common.CommonCallback;
 import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
 import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
@@ -92,7 +93,17 @@ public class ZLMRTPServerFactory {
         return result;
     }
 
-    public int createRTPServer(MediaServerItem mediaServerItem, String streamId, int ssrc, Integer port, Boolean onlyAuto) {
+    /**
+     * 开启rtpServer
+     * @param mediaServerItem zlm服务实例
+     * @param streamId 流Id
+     * @param ssrc ssrc
+     * @param port 端口, 0/null为使用随机
+     * @param reUsePort 是否重用端口
+     * @param tcpMode 0/null udp 模式,1 tcp 被动模式, 2 tcp 主动模式。
+     * @return
+     */
+    public int createRTPServer(MediaServerItem mediaServerItem, String streamId, int ssrc, Integer port, Boolean onlyAuto, Boolean reUsePort, Integer tcpMode) {
         int result = -1;
         // 查询此rtp server 是否已经存在
         JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerItem, streamId);
@@ -108,7 +119,7 @@ public class ZLMRTPServerFactory {
                     JSONObject jsonObject = zlmresTfulUtils.closeRtpServer(mediaServerItem, param);
                     if (jsonObject != null ) {
                         if (jsonObject.getInteger("code") == 0) {
-                            return createRTPServer(mediaServerItem, streamId, ssrc, port, onlyAuto);
+                            return createRTPServer(mediaServerItem, streamId, ssrc, port,onlyAuto, reUsePort, tcpMode);
                         }else {
                             logger.warn("[开启rtpServer], 重启RtpServer错误");
                         }
@@ -122,8 +133,14 @@ public class ZLMRTPServerFactory {
 
         Map<String, Object> param = new HashMap<>();
 
-        param.put("enable_tcp", 1);
+        if (tcpMode == null) {
+            tcpMode = 0;
+        }
+        param.put("tcp_mode", tcpMode);
         param.put("stream_id", streamId);
+        if (reUsePort != null) {
+            param.put("re_use_port", reUsePort?"1":"0");
+        }
         // 推流端口设置0则使用随机端口
         if (port == null) {
             param.put("port", 0);
@@ -169,6 +186,31 @@ public class ZLMRTPServerFactory {
         return result;
     }
 
+    public void closeRtpServer(MediaServerItem serverItem, String streamId, CommonCallback<Boolean> callback) {
+        if (serverItem == null) {
+            callback.run(false);
+            return;
+        }
+        Map<String, Object> param = new HashMap<>();
+        param.put("stream_id", streamId);
+        zlmresTfulUtils.closeRtpServer(serverItem, param, jsonObject -> {
+            if (jsonObject != null ) {
+                if (jsonObject.getInteger("code") == 0) {
+                    callback.run(jsonObject.getInteger("hit") == 1);
+                    return;
+                }else {
+                    logger.error("关闭RTP Server 失败: " + jsonObject.getString("msg"));
+                }
+            }else {
+                //  检查ZLM状态
+                logger.error("关闭RTP Server 失败: 请检查ZLM服务");
+            }
+            callback.run(false);
+        });
+
+
+    }
+
 
     /**
      * 创建一个国标推流
@@ -256,11 +298,14 @@ public class ZLMRTPServerFactory {
         if (jsonObject.getInteger("code") == 0) {
             localPort = jsonObject.getInteger("port");
             HookSubscribeForRtpServerTimeout hookSubscribeForRtpServerTimeout = HookSubscribeFactory.on_rtp_server_timeout(ssrc, null, serverItem.getId());
-            // 订阅 zlm启动事件, 新的zlm也会从这里进入系统
             hookSubscribe.addSubscribe(hookSubscribeForRtpServerTimeout,
                     (MediaServerItem mediaServerItem, JSONObject response)->{
-                        logger.info("[保持端口] {}->监听端口到期继续保持监听", ssrc);
-                        keepPort(serverItem, ssrc);
+                        logger.info("[上级点播] {}->监听端口到期继续保持监听", ssrc);
+                        int port = keepPort(serverItem, ssrc);
+                        if (port == 0) {
+                            logger.info("[上级点播] {}->监听端口失败,移除监听", ssrc);
+                            hookSubscribe.removeSubscribe(hookSubscribeForRtpServerTimeout);
+                        }
                     });
         logger.info("[保持端口] {}->监听端口: {}", ssrc, localPort);
             logger.info("[保持端口] {}->监听端口: {}", ssrc, localPort);
@@ -305,6 +350,9 @@ public class ZLMRTPServerFactory {
      */
     public Boolean isRtpReady(MediaServerItem mediaServerItem, String streamId) {
         JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo(mediaServerItem,"rtp", "rtsp", streamId);
+        if (mediaInfo.getInteger("code") == -2) {
+            return null;
+        }
         return (mediaInfo.getInteger("code") == 0 && mediaInfo.getBoolean("online"));
     }
 
@@ -313,8 +361,10 @@ public class ZLMRTPServerFactory {
      */
     public Boolean isStreamReady(MediaServerItem mediaServerItem, String app, String streamId) {
         JSONObject mediaInfo = zlmresTfulUtils.getMediaList(mediaServerItem, app, streamId);
-        return mediaInfo != null && (mediaInfo.getInteger("code") == 0
-
+        if (mediaInfo == null || (mediaInfo.getInteger("code") == -2)) {
+            return null;
+        }
+        return  (mediaInfo.getInteger("code") == 0
                 && mediaInfo.getJSONArray("data") != null
                 && mediaInfo.getJSONArray("data").size() > 0);
     }
@@ -406,4 +456,19 @@ public class ZLMRTPServerFactory {
         }
         return startSendRtpStreamResult;
     }
+
+    public Boolean updateRtpServerSSRC(MediaServerItem mediaServerItem, String streamId, String ssrc) {
+        boolean result = false;
+        JSONObject jsonObject = zlmresTfulUtils.updateRtpServerSSRC(mediaServerItem, streamId, ssrc);
+        if (jsonObject == null) {
+            logger.error("[更新RTPServer] 失败: 请检查ZLM服务");
+        } else if (jsonObject.getInteger("code") == 0) {
+            result= true;
+            logger.info("[更新RTPServer] 成功");
+        } else {
+            logger.error("[更新RTPServer] 失败: {}, streamId:{},ssrc:{}->\r\n{}",jsonObject.getString("msg"),
+                    streamId, ssrc, jsonObject);
+        }
+        return result;
+    }
 }

+ 6 - 3
src/main/java/com/genersoft/iot/vmp/media/zlm/ZlmHttpHookSubscribe.java

@@ -100,7 +100,10 @@ public class ZlmHttpHookSubscribe {
 
             if (!CollectionUtils.isEmpty(entriesToRemove)) {
                 for (Map.Entry<IHookSubscribe, ZlmHttpHookSubscribe.Event> entry : entriesToRemove) {
-                    entries.remove(entry);
+                    eventMap.remove(entry.getKey());
+                }
+                if (eventMap.size() == 0) {
+                    allSubscribes.remove(hookSubscribe.getHookType());
                 }
             }
 
@@ -136,9 +139,9 @@ public class ZlmHttpHookSubscribe {
     /**
      * 对订阅数据进行过期清理
      */
-    @Scheduled(cron="0 0/5 * * * ?")   //每5分钟执行一次
+//    @Scheduled(cron="0 0/5 * * * ?")   //每5分钟执行一次
+    @Scheduled(fixedRate = 2 * 1000)
     public void execute(){
-
         Instant instant = Instant.now().minusMillis(TimeUnit.MINUTES.toMillis(5));
         int total = 0;
         for (HookType hookType : allSubscribes.keySet()) {

+ 0 - 29
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaServerItem.java

@@ -1,13 +1,10 @@
 package com.genersoft.iot.vmp.media.zlm.dto;
 
 
-import com.genersoft.iot.vmp.gb28181.session.SsrcConfig;
 import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
 import io.swagger.v3.oas.annotations.media.Schema;
 import org.springframework.util.ObjectUtils;
 
-import java.util.HashMap;
-
 @Schema(description = "流媒体服务信息")
 public class MediaServerItem{
 
@@ -80,20 +77,10 @@ public class MediaServerItem{
     @Schema(description = "是否是默认ZLM")
     private boolean defaultServer;
 
-    @Schema(description = "SSRC信息")
-    private SsrcConfig ssrcConfig;
-
     @Schema(description = "当前使用到的端口")
     private int currentPort;
 
 
-    /**
-     * 每一台ZLM都有一套独立的SSRC列表
-     * 在ApplicationCheckRunner里对mediaServerSsrcMap进行初始化
-     */
-    @Schema(description = "ID")
-    private HashMap<String, SsrcConfig> mediaServerSsrcMap;
-
     public MediaServerItem() {
     }
 
@@ -279,22 +266,6 @@ public class MediaServerItem{
         this.updateTime = updateTime;
     }
 
-    public HashMap<String, SsrcConfig> getMediaServerSsrcMap() {
-        return mediaServerSsrcMap;
-    }
-
-    public void setMediaServerSsrcMap(HashMap<String, SsrcConfig> mediaServerSsrcMap) {
-        this.mediaServerSsrcMap = mediaServerSsrcMap;
-    }
-
-    public SsrcConfig getSsrcConfig() {
-        return ssrcConfig;
-    }
-
-    public void setSsrcConfig(SsrcConfig ssrcConfig) {
-        this.ssrcConfig = ssrcConfig;
-    }
-
     public int getCurrentPort() {
         return currentPort;
     }

+ 4 - 0
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResult.java

@@ -18,6 +18,10 @@ public class HookResult {
         return new HookResult(0, "success");
     }
 
+    public static HookResult Fail(){
+        return new HookResult(-1, "fail");
+    }
+
     public int getCode() {
         return code;
     }

+ 31 - 0
src/main/java/com/genersoft/iot/vmp/service/IDeviceChannelService.java

@@ -56,4 +56,35 @@ public interface IDeviceChannelService {
      * 查询通道所属的设备
      */
     List<Device> getDeviceByChannelId(String channelId);
+
+    /**
+     * 批量删除通道
+     * @param deleteChannelList 待删除的通道列表
+     */
+    int deleteChannels(List<DeviceChannel> deleteChannelList);
+
+    /**
+     * 批量上线
+     */
+    int channelsOnline(List<DeviceChannel> channels);
+
+    /**
+     * 批量下线
+     */
+    int channelsOffline(List<DeviceChannel> channels);
+
+    /**
+     *  获取一个通道
+     */
+    DeviceChannel getOne(String deviceId, String channelId);
+
+    /**
+     * 直接批量更新通道
+     */
+    void batchUpdateChannel(List<DeviceChannel> channels);
+
+    /**
+     * 直接批量添加
+     */
+    void batchAddChannel(List<DeviceChannel> deviceChannels);
 }

+ 68 - 0
src/main/java/com/genersoft/iot/vmp/service/IInviteStreamService.java

@@ -0,0 +1,68 @@
+package com.genersoft.iot.vmp.service;
+
+import com.genersoft.iot.vmp.common.InviteInfo;
+import com.genersoft.iot.vmp.common.InviteSessionType;
+import com.genersoft.iot.vmp.service.bean.InviteErrorCallback;
+
+/**
+ * 记录国标点播的状态,包括实时预览,下载,录像回放
+ */
+public interface IInviteStreamService {
+
+    /**
+     * 更新点播的状态信息
+     */
+    void updateInviteInfo(InviteInfo inviteInfo);
+
+    /**
+     * 获取点播的状态信息
+     */
+    InviteInfo getInviteInfo(InviteSessionType type,
+                             String deviceId,
+                             String channelId,
+                             String stream);
+
+    /**
+     * 移除点播的状态信息
+     */
+    void removeInviteInfo(InviteSessionType type,
+                             String deviceId,
+                             String channelId,
+                             String stream);
+    /**
+     * 移除点播的状态信息
+     */
+    void removeInviteInfo(InviteInfo inviteInfo);
+    /**
+     * 移除点播的状态信息
+     */
+    void removeInviteInfoByDeviceAndChannel(InviteSessionType inviteSessionType, String deviceId, String channelId);
+
+    /**
+     * 获取点播的状态信息
+     */
+    InviteInfo getInviteInfoByDeviceAndChannel(InviteSessionType type,
+                             String deviceId,
+                             String channelId);
+
+    /**
+     * 获取点播的状态信息
+     */
+    InviteInfo getInviteInfoByStream(InviteSessionType type, String stream);
+
+
+    /**
+     * 添加一个invite回调
+     */
+    void once(InviteSessionType type, String deviceId, String channelId, String stream,  InviteErrorCallback<Object> callback);
+
+    /**
+     * 调用一个invite回调
+     */
+    void call(InviteSessionType type, String deviceId, String channelId, String stream,  int code, String msg, Object data);
+
+    /**
+     * 清空一个设备的所有invite信息
+     */
+    void clearInviteInfo(String deviceId);
+}

+ 6 - 3
src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java

@@ -1,5 +1,6 @@
 package com.genersoft.iot.vmp.service;
 
+import com.genersoft.iot.vmp.common.CommonCallback;
 import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.media.zlm.dto.ServerKeepaliveData;
@@ -43,14 +44,16 @@ public interface IMediaServerService {
 
     void updateVmServer(List<MediaServerItem>  mediaServerItemList);
 
-    SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, boolean ssrcCheck, boolean isPlayback);
-
-    SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck, boolean isPlayback);
+    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);
 
+    void closeRTPServer(MediaServerItem mediaServerItem, String streamId, CommonCallback<Boolean> callback);
+    Boolean updateRtpServerSSRC(MediaServerItem mediaServerItem, String streamId, String ssrc);
+
     void closeRTPServer(String mediaServerId, String streamId);
 
     void clearRTPServer(MediaServerItem mediaServerItem);

+ 12 - 16
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java

@@ -3,12 +3,11 @@ 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.*;
-import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
-import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
+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.media.zlm.dto.MediaServerItem;
-import com.genersoft.iot.vmp.service.bean.InviteTimeOutCallback;
-import com.genersoft.iot.vmp.service.bean.PlayBackCallback;
+import com.genersoft.iot.vmp.service.bean.InviteErrorCallback;
 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;
@@ -25,12 +24,11 @@ import java.util.Map;
  */
 public interface IPlayService {
 
-    void onPublishHandlerForPlay(MediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId);
-
     void play(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
-              ZlmHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent,
-              InviteTimeOutCallback timeoutCallback);
-    void play(MediaServerItem mediaServerItem, String deviceId, String channelId, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent, Runnable timeoutCallback);
+              InviteErrorCallback<Object> callback);
+    SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId, InviteErrorCallback<Object> callback);
+
+    StreamInfo onPublishHandlerForPlay(MediaServerItem mediaServerItem, JSONObject response, String deviceId, String channelId);
 
     MediaServerItem getNewMediaServerItem(Device device);
 
@@ -39,15 +37,13 @@ public interface IPlayService {
      */
     MediaServerItem getNewMediaServerItemHasAssist(Device device);
 
-    void onPublishHandlerForDownload(InviteStreamInfo inviteStreamInfo, String deviceId, String channelId, String toString);
-
-    void playBack(String deviceId, String channelId, String startTime, String endTime, InviteStreamCallback infoCallBack, PlayBackCallback playBackCallback);
-    void playBack(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, String deviceId, String channelId, String startTime, String endTime, InviteStreamCallback infoCallBack, PlayBackCallback hookCallBack);
+    void playBack(String deviceId, String channelId, String startTime, String endTime, InviteErrorCallback<Object> callback);
+    void playBack(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, String deviceId, String channelId, String startTime, String endTime, InviteErrorCallback<Object> callback);
 
     void zlmServerOffline(String mediaServerId);
 
-    void download(String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, InviteStreamCallback infoCallBack, PlayBackCallback playBackCallback);
-    void download(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo,String deviceId,  String channelId, String startTime, String endTime, int downloadSpeed, InviteStreamCallback infoCallBack, PlayBackCallback hookCallBack);
+    void download(String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, InviteErrorCallback<Object> callback);
+    void download(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo,String deviceId,  String channelId, String startTime, String endTime, int downloadSpeed, InviteErrorCallback<Object> callback);
 
     StreamInfo getDownLoadInfo(String deviceId, String channelId, String stream);
 

+ 6 - 0
src/main/java/com/genersoft/iot/vmp/service/bean/InviteErrorCallback.java

@@ -0,0 +1,6 @@
+package com.genersoft.iot.vmp.service.bean;
+
+public interface InviteErrorCallback<T> {
+
+    void run(int code, String msg, T data);
+}

+ 0 - 0
src/main/java/com/genersoft/iot/vmp/service/bean/InviteErrorCode.java


Some files were not shown because too many files changed in this diff