Merge branch 'master' into 结构优化

# Conflicts:
#	src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpItem.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/task/SipRunner.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/info/InfoRequestProcessor.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java
#	src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
#	src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerFactory.java
#	src/main/java/com/genersoft/iot/vmp/service/IMediaService.java
#	src/main/java/com/genersoft/iot/vmp/service/IPlatformService.java
#	src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java
#	src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java
#	src/main/java/com/genersoft/iot/vmp/service/impl/PlatformChannelServiceImpl.java
#	src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java
#	src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
#	src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java
#	src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamCloseResponseListener.java
#	src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
#	src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java
#	数据库/初始化-postgresql-kingbase.sql
This commit is contained in:
648540858 2024-03-09 21:37:54 +08:00
commit 3c092f4195
82 changed files with 7358 additions and 3777 deletions

View File

@ -1,130 +0,0 @@
#很久没维护了,已经与定前版本不匹配
FROM ubuntu:20.04 AS build
ARG DEBIAN_FRONTEND=noninteractive
ENV TZ=Asia/Shanghai
EXPOSE 18080/tcp
EXPOSE 5060/tcp
EXPOSE 5060/udp
EXPOSE 6379/tcp
EXPOSE 18081/tcp
EXPOSE 80/tcp
EXPOSE 1935/tcp
EXPOSE 554/tcp
EXPOSE 554/udp
EXPOSE 30000-30500/tcp
EXPOSE 30000-30500/udp
ENV LC_ALL zh_CN.UTF-8
# 使用了自己的settings.xml作为maven的源,加快打包速度
RUN apt-get update && \
DEBIAN_FRONTEND="noninteractive" && \
apt-get install -y --no-install-recommends openjdk-11-jre git maven nodejs npm build-essential tcl language-pack-zh-hans \
cmake curl vim ca-certificates tzdata libmysqlclient-dev redis-server libssl-dev libx264-dev libfaac-dev ffmpeg
WORKDIR /home
RUN git clone https://gitee.com/pan648540858/maven.git && \
cp maven/settings.xml /usr/share/maven/conf/ && \
git clone https://gitee.com/pan648540858/wvp-GB28181.git && \
git clone https://gitee.com/pan648540858/wvp-pro-assist.git
# 编译前端界面
WORKDIR /home/wvp-GB28181/web_src
RUN npm install && \
npm run build && \
mkdir -p /opt/wvp/config && \
mkdir -p /opt/assist/config && \
cp /home/wvp-GB28181/src/main/resources/application-dev.yml /opt/wvp/config/application.yml && \
cp /home/wvp-pro-assist/src/main/resources/application-dev.yml /opt/assist/config/application.yml
# wvp打包
WORKDIR /home/wvp-GB28181
RUN mvn compile && \
mvn package && \
cp /home/wvp-GB28181/target/wvp*.jar /opt/wvp/
# wvp 录像管理打包
WORKDIR /home/wvp-pro-assist
RUN mvn compile && \
mvn package && \
cp /home/wvp-pro-assist/target/*.jar /opt/assist/
# zlm打包
WORKDIR /home
RUN mkdir -p /opt/media && \
git clone --depth=1 https://gitee.com/xia-chu/ZLMediaKit && \
cd ZLMediaKit && git submodule update --init --recursive && \
mkdir -p build release/linux/Release/ &&\
cd build && \
cmake -DCMAKE_BUILD_TYPE=Release .. && \
make -j4 && \
rm -rf ../release/linux/Release/config.ini && \
cp -r ../release/linux/Release/* /opt/media && \
mkdir -p /opt/media/www/record
# 清理
RUN rm -rf /home/wiki && \
rm -rf /home/wvp-GB28181 && \
apt-get autoremove -y git maven nodejs npm && \
apt-get clean -y && \
rm -rf /var/lib/apt/lists/*dic
WORKDIR /opt/wvp
RUN echo '#!/bin/bash' > run.sh && \
echo 'echo ${WVP_IP}' >> run.sh && \
echo 'echo ${WVP_CONFIG}' >> run.sh && \
echo 'redis-server --daemonize yes --bind 0.0.0.0' >> run.sh && \
echo 'cd /opt/assist' >> run.sh && \
echo 'nohup java -jar *.jar --userSettings.record=/opt/media/www/record/ &' >> run.sh && \
echo 'nohup /opt/media/MediaServer -d -m 3 &' >> run.sh && \
echo 'cd /opt/wvp' >> run.sh && \
echo 'if [-n "${WVP_CONFIG}"]; then' >> run.sh && \
echo ' java -jar *.jar --spring.config.location=/opt/wvp/config/application.yml --media.record-assist-port=18081 ${WVP_CONFIG}' >> run.sh && \
echo 'else' >> run.sh && \
echo ' java -jar *.jar --spring.config.location=/opt/wvp/config/application.yml --media.record-assist-port=18081 --media.ip=127.0.0.1 --media.sdp-ip=${WVP_IP} --sip.ip=${WVP_IP} --media.stream-ip=${WVP_IP}' >> run.sh && \
echo 'fi' >> run.sh
RUN chmod +x run.sh
FROM ubuntu:20.04
ARG DEBIAN_FRONTEND=noninteractive
ENV TZ=Asia/Shanghai
EXPOSE 18080/tcp
EXPOSE 5060/tcp
EXPOSE 5060/udp
EXPOSE 6379/tcp
EXPOSE 18081/tcp
EXPOSE 80/tcp
EXPOSE 1935/tcp
EXPOSE 554/tcp
EXPOSE 554/udp
EXPOSE 30000-30500/tcp
EXPOSE 30000-30500/udp
ENV LC_ALL zh_CN.UTF-8
RUN apt-get update && \
DEBIAN_FRONTEND="noninteractive" && \
apt-get install -y --no-install-recommends openjdk-11-jre tcl language-pack-zh-hans \
ca-certificates tzdata libmysqlclient21 redis-server libssl1.1 libx264-155 libfaac0 ffmpeg && \
apt-get autoremove -y && \
apt-get clean -y && \
rm -rf /var/lib/apt/lists/*dic
WORKDIR /opt/wvp
COPY --from=build /opt /opt
CMD ["sh", "run.sh"]

View File

@ -68,6 +68,7 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git
- [X] 支持国标网络校时 - [X] 支持国标网络校时
- [X] 支持播放H264和H265 - [X] 支持播放H264和H265
- [X] 报警信息处理,支持向前端推送报警信息 - [X] 报警信息处理,支持向前端推送报警信息
- [X] 语音对讲
- [X] 支持订阅与通知方法 - [X] 支持订阅与通知方法
- [X] 移动位置订阅 - [X] 移动位置订阅
- [X] 移动位置通知处理 - [X] 移动位置通知处理
@ -94,6 +95,7 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git
- [X] 目录订阅与通知 - [X] 目录订阅与通知
- [X] 录像查看与播放 - [X] 录像查看与播放
- [X] GPS订阅与通知直播推流 - [X] GPS订阅与通知直播推流
- [X] 语音对讲
- [X] 支持自动配置ZLM媒体服务, 减少因配置问题所出现的问题; - [X] 支持自动配置ZLM媒体服务, 减少因配置问题所出现的问题;
- [X] 多流媒体节点,自动选择负载最低的节点使用。 - [X] 多流媒体节点,自动选择负载最低的节点使用。
- [X] 支持启用udp多端口模式, 提高udp模式下媒体传输性能; - [X] 支持启用udp多端口模式, 提高udp模式下媒体传输性能;
@ -135,3 +137,11 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git
[mk1990](https://github.com/mk1990) [SaltFish001](https://github.com/SaltFish001) [mk1990](https://github.com/mk1990) [SaltFish001](https://github.com/SaltFish001)
ffmpeg -re -i 123.mp3 -acodec pcm_alaw -ar 8000 -ac 1 -f rtsp rtsp://192.168.1.3:30554/broadcast/34020000001320000101_34020000001310000001
ffmpeg -re -i 123.mp3 -acodec pcm_alaw -ar 8000 -ac 1 -f rtsp rtsp://192.168.1.3:30554/talk/34020000001320000011_34020000001370000001
ffmpeg -re -i 123.mp3 -acodec pcm_alaw -ar 8000 -ac 1 -f rtsp rtsp://192.168.1.3:30554/talk/34020000001320000101_34020000001310000001

View File

@ -55,8 +55,8 @@
- [X] 移动设备位置订阅 - [X] 移动设备位置订阅
- [X] 报警订阅 - [X] 报警订阅
- [X] 目录订阅 - [X] 目录订阅
- [ ] 语音广播 - [X] 语音广播
- [ ] 语音对讲 - [X] 语音喊话
**作为下级平台** **作为下级平台**
- [X] 注册 - [X] 注册
@ -94,8 +94,8 @@
- [X] 移动设备位置订阅 - [X] 移动设备位置订阅
- [ ] 报警订阅 - [ ] 报警订阅
- [X] 目录订阅 - [X] 目录订阅
- [ ] 语音广播 - [X] 语音广播
- [ ] 语音对讲 - [X] 语音喊话

View File

@ -0,0 +1,76 @@
# 语音对讲
## 流程和原理
语音对讲在国标28181-2016中分为broadcast广播和talk对讲两种模式broadcast模式是从服务端把音频传送到设备端是单向的
需要结合点播视频来实现双向对讲talk模式支持双向不过wvp只处理了和broadcast一样的把音频传递设备这样两种模式可以使用一样的逻辑处理即可。
不同的设备对于两种模式的支持不同且通常差异很大不同的设备对同一个设备的支持也有一些不同所以语音对讲中的兼容和适配也是问题最多的。talk模式因为在国标28181-2022中已经移除所以这里不再讨论它了。
### 1. broadcast模式流程
```plantuml
@startuml
"WVP-PRO" -> "设备": 语音广播通知
"WVP-PRO" <-- "设备": 200OK
"WVP-PRO" <- "设备": 语音广播应答
"WVP-PRO" --> "设备": 200OK
"WVP-PRO" <- "设备": Invite
"WVP-PRO" --> "设备": 200OK(携带SDP消息体)
"WVP-PRO" <-- "设备": ACK
"ZLMediaKit" -> "设备": 向设备发送语音流
@enduml
```
与点播的流程不同的是这里的invite消息是由设备发送给wvp的wvp按照invite协商的方式给设备推送语音流所有对讲的使用那种方式UDP/TCP被动/TCP主动传输语音流由设备决定
## 使用条件与限制
因为invite消息是由设备发送给wvp的这决定了发送语音流的方式这也就决定了有的设备不能用于公网对讲比如大部分的海康设备只支持udp方式收流(目前新版的海康设备已经在着手解决这个问题)那么wvp发流时只能按照sdp中指定的ip端口发流所以如果wvp在公网设备在内网中那么wvp无法连接设备提供的IP发流也就失败了。
与海康不同的大华以及很多执法记录仪厂商是支持tcp主动方式取流的这样是可以实现公网对讲的。
## 使用ffmpeg快速测试
由于浏览器对于音频的采集需要网页支持https才可以所以如果想要实现网页音频对讲那么你必须给wvp和zlm配置证书以使用https。
测试阶段如果只是想测试功能可以用ffmpeg来模拟语音流推送到wvp后可以实现把音频文件推送到摄像头。
测试命令格式如下:
```shell
ffmpeg -re -i {音频文件} -acodec pcm_alaw -ar 8000 -ac 1 -f rtsp 'rtsp://{zlm的IP}:{zlm的RTSP端口}/broadcast/{设备国标编号}_{通道国标编号}?sign={md5(pushKey)}'
```
例如
```shell
ffmpeg -re -i test.mp3 -acodec pcm_alaw -ar 8000 -ac 1 -f rtsp 'rtsp://192.168.1.3:22554/broadcast/34020000001320000001_34020000001320000001?sign=41db35390ddad33f83944f44b8b75ded'
```
测试流程如下:
```plantuml
@startuml
"FFMPEG" -> "ZLMediaKit": 推流到zlm
"WVP-PRO" <- "ZLMediaKit": 通知收到语音对讲推流携带设备和通道信息
"WVP-PRO" -> "设备": 开始语音对讲
"WVP-PRO" <-- "设备": 语音对讲建立成功携带收流端口
"WVP-PRO" -> "ZLMediaKit": 通知zlm将流推送到设备收流端口
"ZLMediaKit" -> "设备": 向设备推流
@enduml
```
如果听到设备播放你推送的音频,那么意味着调用成功,此过程推流即可需要调用任何接口
## 生产环境网页发起语音对讲
生产环境下使用语音对讲如果是自己的客户端设备那么直接上面的ffmpeg测试方式按照固定格式推流到zlm即可。
对于WEB程序主要是局域网和公网的区别两个原因
1. 很多设备不支持公网对讲
2. 公网和局域网获取证书实现https支持的方式不同
### 公网使用
公网你可以直接使用证书厂商或者云服务器厂商提供的证书,这是很方便的。
### 局域网使用
局域网你需要为wvp和zlm生成自签名证书这里我推荐一种生成自签名证书相对方便的方式,
此方式为linux下的一种方式。
下载证书生成工具:
[https://github.com/FiloSottile/mkcert/releases/tag/v1.4.4](https://github.com/FiloSottile/mkcert/releases/tag/v1.4.4)
安装此工具, 进入解压的工具目录,执行
```shell
./mkcert-v1.4.4-linux-amd64 -install
```
生成pem证书
```shell
./mkcert-v1.4.4-linux-amd64 局域网IP 局域网IP2 局域网IP3
```
你会得到两文件*-key.pem和*.pem, 此文件配置到wvp后既可实现证书的加载
生成zlm使用的证书
```shell
cat *.pem *-key.pem> ./zlm.pem
```
得到的文件就是可以给zlm使用的证书
zlm下使用证书有两种方式
1. 替换zlm下的default.pem, 即删除此文件并把zlm.pem重命名为default.pem
2. 在启动zlm的使用添加 `-s zlm.pem`

27
doc/_content/broadcast.md Normal file
View File

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

View File

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

View File

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

View File

@ -15,11 +15,13 @@
* [节点管理](_content/ability/node_manger.md) * [节点管理](_content/ability/node_manger.md)
* [云端录像](_content/ability/cloud_record.md) * [云端录像](_content/ability/cloud_record.md)
* [不间断录像](_content/ability/continuous_recording.md) * [不间断录像](_content/ability/continuous_recording.md)
* [语音对讲](_content/ability/continuous_broadcast.md)
* **流程与原理** * **流程与原理**
* [统一编码规则](_content/theory/code.md) * [统一编码规则](_content/theory/code.md)
* [树形结构](_content/theory/channel_tree.md) * [树形结构](_content/theory/channel_tree.md)
* [注册流程](_content/theory/register.md) * [注册流程](_content/theory/register.md)
* [点播流程](_content/theory/play.md) * [点播流程](_content/theory/play.md)
* [级联语音喊话流程](_content/theory/broadcast_cascade.md)
* **必备技巧** * **必备技巧**
* [抓包](_content/skill/tcpdump.md) * [抓包](_content/skill/tcpdump.md)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -206,6 +206,8 @@ public class Device {
public void setId(int id) { public void setId(int id) {
this.id = id; this.id = id;
} }
@Schema(description = "控制语音对讲流程释放收到ACK后发流")
private boolean broadcastPushAfterAck;
public String getDeviceId() { public String getDeviceId() {
return deviceId; return deviceId;
@ -468,6 +470,9 @@ public class Device {
public void setSipTransactionInfo(SipTransactionInfo sipTransactionInfo) { public void setSipTransactionInfo(SipTransactionInfo sipTransactionInfo) {
this.sipTransactionInfo = sipTransactionInfo; this.sipTransactionInfo = sipTransactionInfo;
} }
public boolean isBroadcastPushAfterAck() {
return broadcastPushAfterAck;
}
public boolean isAutoSyncChannel() { public boolean isAutoSyncChannel() {
return autoSyncChannel; return autoSyncChannel;
@ -476,4 +481,7 @@ public class Device {
public void setAutoSyncChannel(boolean autoSyncChannel) { public void setAutoSyncChannel(boolean autoSyncChannel) {
this.autoSyncChannel = autoSyncChannel; this.autoSyncChannel = autoSyncChannel;
} }
public void setBroadcastPushAfterAck(boolean broadcastPushAfterAck) {
this.broadcastPushAfterAck = broadcastPushAfterAck;
}
} }

View File

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

View File

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

View File

@ -51,7 +51,7 @@ public class SendRtpItem {
/** /**
* 设备推流的streamId * 设备推流的streamId
*/ */
private String streamId; private String stream;
/** /**
* 是否为tcp * 是否为tcp
@ -119,6 +119,11 @@ public class SendRtpItem {
*/ */
private InviteStreamType playType; private InviteStreamType playType;
/**
* 发流的同时收流
*/
private String receiveStream;
public String getIp() { public String getIp() {
return ip; return ip;
} }
@ -175,12 +180,12 @@ public class SendRtpItem {
this.app = app; this.app = app;
} }
public String getStreamId() { public String getStream() {
return streamId; return stream;
} }
public void setStreamId(String streamId) { public void setStream(String stream) {
this.streamId = streamId; this.stream = stream;
} }
public boolean isTcp() { public boolean isTcp() {
@ -302,4 +307,12 @@ public class SendRtpItem {
public void setId(String id) { public void setId(String id) {
this.id = id; this.id = id;
} }
public String getReceiveStream() {
return receiveStream;
}
public void setReceiveStream(String receiveStream) {
this.receiveStream = receiveStream;
}
} }

View File

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

View File

@ -78,8 +78,10 @@ public class SipSubscribe {
dialogTerminated, dialogTerminated,
// 设备未找到 // 设备未找到
deviceNotFoundEvent, deviceNotFoundEvent,
// 设备未找到 // 消息发送失败
cmdSendFailEvent cmdSendFailEvent,
// 消息发送失败
failedToGetPort
} }
public static class EventResult<EventObject>{ public static class EventResult<EventObject>{

View File

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

View File

@ -113,7 +113,7 @@ public class SipRunner implements CommandLineRunner {
Map<String, Object> param = new HashMap<>(); Map<String, Object> param = new HashMap<>();
param.put("vhost","__defaultVhost__"); param.put("vhost","__defaultVhost__");
param.put("app",sendRtpItem.getApp()); param.put("app",sendRtpItem.getApp());
param.put("stream",sendRtpItem.getStreamId()); param.put("stream",sendRtpItem.getStream());
param.put("ssrc",sendRtpItem.getSsrc()); param.put("ssrc",sendRtpItem.getSsrc());
JSONObject jsonObject = zlmresTfulUtils.stopSendRtp(mediaServerItem, param); JSONObject jsonObject = zlmresTfulUtils.stopSendRtp(mediaServerItem, param);
if (jsonObject != null && jsonObject.getInteger("code") == 0) { if (jsonObject != null && jsonObject.getInteger("code") == 0) {

View File

@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd;
import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; 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.Device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm; import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
import com.genersoft.iot.vmp.gb28181.bean.command.PTZCommand; import com.genersoft.iot.vmp.gb28181.bean.command.PTZCommand;
@ -67,13 +68,19 @@ public interface ISIPCommander {
String startTime, String endTime, int downloadSpeed, ZlmHttpHookSubscribe.Event hookEvent, String startTime, String endTime, int downloadSpeed, ZlmHttpHookSubscribe.Event hookEvent,
SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException; SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
/** /**
* 视频流停止 * 视频流停止
*/ */
void streamByeCmd(Device device, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException; void streamByeCmd(Device device, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException;
void talkStreamCmd(MediaServerItem mediaServerItem, SendRtpItem sendRtpItem, Device device, String channelId, String callId, ZlmHttpHookSubscribe.Event event, ZlmHttpHookSubscribe.Event eventForPush, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
void streamByeCmd(Device device, String channelId, String stream, String callId) throws InvalidArgumentException, ParseException, SipException, SsrcTransactionNotFoundException; void streamByeCmd(Device device, String channelId, String stream, String callId) throws InvalidArgumentException, ParseException, SipException, SsrcTransactionNotFoundException;
void streamByeCmd(Device device, String channelId, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException;
/** /**
* 回放暂停 * 回放暂停
*/ */
@ -103,21 +110,15 @@ public interface ISIPCommander {
void playbackControlCmd(Device device, StreamInfo streamInfo, String content,SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException; void playbackControlCmd(Device device, StreamInfo streamInfo, String content,SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException;
/** void streamByeCmdForDeviceInvite(Device device, String channelId, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException;
* 语音广播
*
* @param device 视频设备
* @param channelId 预览通道
*/
void audioBroadcastCmd(Device device,String channelId);
/** /**
* /**
* 语音广播 * 语音广播
* *
* @param device 视频设备 * @param device 视频设备
*/ */
void audioBroadcastCmd(Device device, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException; void audioBroadcastCmd(Device device, String channelId, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
void audioBroadcastCmd(Device device) throws InvalidArgumentException, SipException, ParseException;
/** /**
* 音视频录像控制 * 音视频录像控制
@ -305,4 +306,5 @@ public interface ISIPCommander {
*/ */
void sendAlarmMessage(Device device, DeviceAlarm deviceAlarm) throws InvalidArgumentException, SipException, ParseException; void sendAlarmMessage(Device device, DeviceAlarm deviceAlarm) throws InvalidArgumentException, SipException, ParseException;
} }

View File

@ -1,9 +1,13 @@
package com.genersoft.iot.vmp.gb28181.transmit.cmd; package com.genersoft.iot.vmp.gb28181.transmit.cmd;
import com.genersoft.iot.vmp.common.CommonGbChannel; import com.genersoft.iot.vmp.common.CommonGbChannel;
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
import javax.sip.InvalidArgumentException; import javax.sip.InvalidArgumentException;
import javax.sip.SipException; import javax.sip.SipException;
@ -15,6 +19,7 @@ public interface ISIPCommanderForPlatform {
/** /**
* 向上级平台注册 * 向上级平台注册
*
* @param parentPlatform * @param parentPlatform
* @return * @return
*/ */
@ -27,6 +32,7 @@ public interface ISIPCommanderForPlatform {
/** /**
* 向上级平台注销 * 向上级平台注销
*
* @param parentPlatform * @param parentPlatform
* @return * @return
*/ */
@ -35,14 +41,17 @@ public interface ISIPCommanderForPlatform {
/** /**
* 向上级平发送心跳信息 * 向上级平发送心跳信息
*
* @param parentPlatform * @param parentPlatform
* @return callId(作为接受回复的判定) * @return callId(作为接受回复的判定)
*/ */
String keepalive(ParentPlatform parentPlatform,SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException; String keepalive(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent)
throws SipException, InvalidArgumentException, ParseException;
/** /**
* 向上级回复通道信息 * 向上级回复通道信息
*
* @param channel 通道信息 * @param channel 通道信息
* @param parentPlatform 平台信息 * @param parentPlatform 平台信息
* @param sn * @param sn
@ -55,6 +64,7 @@ public interface ISIPCommanderForPlatform {
/** /**
* 向上级回复DeviceInfo查询信息 * 向上级回复DeviceInfo查询信息
*
* @param parentPlatform 平台信息 * @param parentPlatform 平台信息
* @param sn SN * @param sn SN
* @param fromTag FROM头的tag信息 * @param fromTag FROM头的tag信息
@ -64,6 +74,7 @@ public interface ISIPCommanderForPlatform {
/** /**
* 向上级回复DeviceStatus查询信息 * 向上级回复DeviceStatus查询信息
*
* @param parentPlatform 平台信息 * @param parentPlatform 平台信息
* @param sn * @param sn
* @param fromTag * @param fromTag
@ -73,15 +84,18 @@ public interface ISIPCommanderForPlatform {
/** /**
* 向上级回复移动位置订阅消息 * 向上级回复移动位置订阅消息
*
* @param parentPlatform 平台信息 * @param parentPlatform 平台信息
* @param gpsMsgInfo GPS信息 * @param gpsMsgInfo GPS信息
* @param subscribeInfo 订阅相关的信息 * @param subscribeInfo 订阅相关的信息
* @return * @return
*/ */
void sendNotifyMobilePosition(ParentPlatform parentPlatform, GPSMsgInfo gpsMsgInfo, SubscribeInfo subscribeInfo) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException; void sendNotifyMobilePosition(ParentPlatform parentPlatform, GPSMsgInfo gpsMsgInfo, SubscribeInfo subscribeInfo)
throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException;
/** /**
* 向上级回复报警消息 * 向上级回复报警消息
*
* @param parentPlatform 平台信息 * @param parentPlatform 平台信息
* @param deviceAlarm 报警信息信息 * @param deviceAlarm 报警信息信息
* @return * @return
@ -109,6 +123,7 @@ public interface ISIPCommanderForPlatform {
/** /**
* 录像播放推送完成时发送MediaStatus消息 * 录像播放推送完成时发送MediaStatus消息
*
* @param platform * @param platform
* @param sendRtpItem * @param sendRtpItem
* @return * @return
@ -117,9 +132,19 @@ public interface ISIPCommanderForPlatform {
/** /**
* 向发起点播的上级回复bye * 向发起点播的上级回复bye
*
* @param platform 平台信息 * @param platform 平台信息
* @param callId callId * @param callId callId
*/ */
void streamByeCmd(ParentPlatform platform, String callId) throws SipException, InvalidArgumentException, ParseException; void streamByeCmd(ParentPlatform platform, String callId) throws SipException, InvalidArgumentException, ParseException;
void streamByeCmd(ParentPlatform platform, SendRtpItem sendRtpItem) throws SipException, InvalidArgumentException, ParseException; void streamByeCmd(ParentPlatform platform, SendRtpItem sendRtpItem) throws SipException, InvalidArgumentException, ParseException;
void streamByeCmd(ParentPlatform platform, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException;
void broadcastInviteCmd(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem,
SSRCInfo ssrcInfo, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent,
SipSubscribe.Event errorEvent) throws ParseException, SipException, InvalidArgumentException;
void broadcastResultCmd(ParentPlatform platform, DeviceChannel deviceChannel, String sn, boolean result, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
} }

View File

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

View File

@ -197,6 +197,41 @@ public class SIPRequestHeaderProvider {
return request; return request;
} }
public Request createByteRequestForDeviceInvite(Device device, String channelId, SipTransactionInfo transactionInfo) throws ParseException, InvalidArgumentException, PeerUnavailableException {
Request request = null;
//请求行
SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
// via
ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag());
viaHeaders.add(viaHeader);
//from
SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain());
Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.getToTag());
//to
SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId,device.getHostAddress());
Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, transactionInfo.getFromTag());
//Forwards
MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
//ceq
CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE);
CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId());
request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.BYE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
return request;
}
public Request createSubscribeRequest(Device device, String content, SIPRequest requestOld, Integer expires, String event, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException { public Request createSubscribeRequest(Device device, String content, SIPRequest requestOld, Integer expires, String event, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
Request request = null; Request request = null;
// sipuri // sipuri
@ -315,6 +350,38 @@ public class SIPRequestHeaderProvider {
request.addHeader(SipUtils.createUserAgentHeader(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 = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
// via
ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipConfig.getIp(), sipConfig.getPort(), device.getTransport(), viaTag);
viaHeader.setRPort();
viaHeaders.add(viaHeader);
// from
SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, fromTag);
// to
SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, toTag);
// Forwards
MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
// ceq
CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.MESSAGE);
ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
request = SipFactory.getInstance().createMessageFactory().createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader,
toHeader, viaHeaders, maxForwards, contentTypeHeader, content);
request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
return request; return request;
} }
} }

View File

@ -6,6 +6,7 @@ import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
import com.genersoft.iot.vmp.gb28181.SipLayer; import com.genersoft.iot.vmp.gb28181.SipLayer;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm; import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
@ -18,9 +19,11 @@ import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider; import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider;
import com.genersoft.iot.vmp.gb28181.utils.NumericUtil; import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe; 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.HookSubscribeFactory;
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange; import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamPush;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.hook.HookParam; import com.genersoft.iot.vmp.media.zlm.dto.hook.HookParam;
import com.genersoft.iot.vmp.service.IMediaServerService; import com.genersoft.iot.vmp.service.IMediaServerService;
@ -39,6 +42,7 @@ import javax.sip.*;
import javax.sip.header.CallIdHeader; import javax.sip.header.CallIdHeader;
import javax.sip.message.Request; import javax.sip.message.Request;
import java.text.ParseException; import java.text.ParseException;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
@ -73,16 +77,80 @@ public class SIPCommander implements ISIPCommander {
@Autowired @Autowired
private ZlmHttpHookSubscribe subscribe; private ZlmHttpHookSubscribe subscribe;
@Autowired @Autowired
private IMediaServerService mediaServerService; private IMediaServerService mediaServerService;
@Autowired
private ZLMServerFactory zlmServerFactory;
@Override @Override
public void ptzCmd(Device device, String channelId, PTZCommand ptzCommand) throws InvalidArgumentException, SipException, ParseException { public void ptzZoomCmd(Device device, String channelId, int inOut, int zoomSpeed) throws InvalidArgumentException, ParseException, SipException {
String cmdStr = SipUtils.cmdString(ptzCommand); ptzCmd(device, channelId, 0, 0, inOut, 0, zoomSpeed);
frontEndCmd(device, channelId, cmdStr); }
/**
* 云台指令码计算
*
* @param cmdCode 指令码
* @param parameter1 数据1
* @param parameter2 数据2
* @param combineCode2 组合码2
*/
public static String frontEndCmdString(int cmdCode, int parameter1, int parameter2, int combineCode2) {
StringBuilder builder = new StringBuilder("A50F01");
String strTmp;
strTmp = String.format("%02X", cmdCode);
builder.append(strTmp, 0, 2);
strTmp = String.format("%02X", parameter1);
builder.append(strTmp, 0, 2);
strTmp = String.format("%02X", parameter2);
builder.append(strTmp, 0, 2);
//优化zoom变倍速率
if ((combineCode2 > 0) && (combineCode2 <16))
{
combineCode2 = 16;
}
strTmp = String.format("%X", combineCode2);
builder.append(strTmp, 0, 1).append("0");
//计算校验码
int checkCode = (0XA5 + 0X0F + 0X01 + cmdCode + parameter1 + parameter2 + (combineCode2 & 0XF0)) % 0X100;
strTmp = String.format("%02X", checkCode);
builder.append(strTmp, 0, 2);
return builder.toString();
}
/**
* 云台控制支持方向与缩放控制
*
* @param device 控制设备
* @param channelId 预览通道
* @param leftRight 镜头左移右移 0:停止 1:左移 2:右移
* @param upDown 镜头上移下移 0:停止 1:上移 2:下移
* @param inOut 镜头放大缩小 0:停止 1:缩小 2:放大
* @param moveSpeed 镜头移动速度
* @param zoomSpeed 镜头缩放速度
*/
@Override
public void ptzCmd(Device device, String channelId, int leftRight, int upDown, int inOut, int moveSpeed,
int zoomSpeed) throws InvalidArgumentException, SipException, ParseException {
String cmdStr = SipUtils.cmdString(leftRight, upDown, inOut, moveSpeed, zoomSpeed);
StringBuilder ptzXml = new StringBuilder(200);
String charset = device.getCharset();
ptzXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
ptzXml.append("<Control>\r\n");
ptzXml.append("<CmdType>DeviceControl</CmdType>\r\n");
ptzXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
ptzXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
ptzXml.append("<PTZCmd>" + cmdStr + "</PTZCmd>\r\n");
ptzXml.append("<Info>\r\n");
ptzXml.append("<ControlPriority>5</ControlPriority>\r\n");
ptzXml.append("</Info>\r\n");
ptzXml.append("</Control>\r\n");
Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request);
} }
/** /**
@ -450,6 +518,70 @@ public class SIPCommander implements ISIPCommander {
}); });
} }
@Override
public void talkStreamCmd(MediaServerItem mediaServerItem, SendRtpItem sendRtpItem, Device device, String channelId, String callId, ZlmHttpHookSubscribe.Event event, ZlmHttpHookSubscribe.Event eventForPush, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
String stream = sendRtpItem.getStream();
if (device == null) {
return;
}
if (!mediaServerItem.isRtpEnable()) {
// 单端口暂不支持语音喊话
logger.info("[语音喊话] 单端口暂不支持此操作");
return;
}
logger.info("[语音喊话] {} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), sendRtpItem.getPort());
HookSubscribeForStreamChange hookSubscribeForStreamChange = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId());
subscribe.addSubscribe(hookSubscribeForStreamChange, (mediaServerItemInUse, hookParam) -> {
if (event != null) {
event.response(mediaServerItemInUse, hookParam);
subscribe.removeSubscribe(hookSubscribeForStreamChange);
}
});
CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()), device.getTransport());
callIdHeader.setCallId(callId);
HookSubscribeForStreamPush hookSubscribeForStreamPush = HookSubscribeFactory.on_publish("rtp", stream, null, mediaServerItem.getId());
subscribe.addSubscribe(hookSubscribeForStreamPush, (mediaServerItemInUse, hookParam) -> {
if (eventForPush != null) {
eventForPush.response(mediaServerItemInUse, hookParam);
}
});
//
StringBuffer content = new StringBuffer(200);
content.append("v=0\r\n");
content.append("o=" + channelId + " 0 0 IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
content.append("s=Talk\r\n");
content.append("c=IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
content.append("t=0 0\r\n");
content.append("m=audio " + sendRtpItem.getPort() + " TCP/RTP/AVP 8\r\n");
content.append("a=setup:passive\r\n");
content.append("a=connection:new\r\n");
content.append("a=sendrecv\r\n");
content.append("a=rtpmap:8 PCMA/8000\r\n");
content.append("y=" + sendRtpItem.getSsrc() + "\r\n");//ssrc
// f字段:f= v/编码格式/分辨率/帧率/码率类型/码率大小a/编码格式/码率大小/采样率
content.append("f=v/////a/1/8/1" + "\r\n");
Request request = headerProvider.createInviteRequest(device, channelId, content.toString(),
SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, sendRtpItem.getSsrc(), callIdHeader);
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, (e -> {
streamSession.remove(device.getDeviceId(), channelId, sendRtpItem.getStream());
mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc());
errorEvent.response(e);
}), e -> {
// 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值
ResponseEvent responseEvent = (ResponseEvent) e.event;
SIPResponse response = (SIPResponse) responseEvent.getResponse();
streamSession.put(device.getDeviceId(), channelId, "talk", stream, sendRtpItem.getSsrc(), mediaServerItem.getId(), response, InviteSessionType.TALK);
okEvent.response(e);
});
}
/** /**
* 视频流停止, 不使用回调 * 视频流停止, 不使用回调
*/ */
@ -481,18 +613,19 @@ public class SIPCommander implements ISIPCommander {
streamSession.removeByCallId(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getCallId()); streamSession.removeByCallId(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getCallId());
Request byteRequest = headerProvider.createByteRequest(device, channelId, ssrcTransaction.getSipTransactionInfo()); Request byteRequest = headerProvider.createByteRequest(device, channelId, ssrcTransaction.getSipTransactionInfo());
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), byteRequest, null, okEvent); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), byteRequest, null, okEvent);
} }
} }
/**
* 语音广播
*
* @param device 视频设备
* @param channelId 预览通道
*/
@Override @Override
public void audioBroadcastCmd(Device device, String channelId) { public void streamByeCmd(Device device, String channelId, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException {
Request byteRequest = headerProvider.createByteRequest(device, channelId, sipTransactionInfo);
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), byteRequest, null, okEvent);
}
@Override
public void streamByeCmdForDeviceInvite(Device device, String channelId, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException {
Request byteRequest = headerProvider.createByteRequestForDeviceInvite(device, channelId, sipTransactionInfo);
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), byteRequest, null, okEvent);
} }
/** /**
@ -528,15 +661,13 @@ public class SIPCommander implements ISIPCommander {
broadcastXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n"); broadcastXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
broadcastXml.append("<Notify>\r\n"); broadcastXml.append("<Notify>\r\n");
broadcastXml.append("<CmdType>Broadcast</CmdType>\r\n"); broadcastXml.append("<CmdType>Broadcast</CmdType>\r\n");
broadcastXml.append("<SN>" + SipUtils.getNewSn() + "</SN>\r\n"); broadcastXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
broadcastXml.append("<SourceID>" + sipConfig.getId() + "</SourceID>\r\n"); broadcastXml.append("<SourceID>" + sipConfig.getId() + "</SourceID>\r\n");
broadcastXml.append("<TargetID>" + device.getDeviceId() + "</TargetID>\r\n"); broadcastXml.append("<TargetID>" + channelId + "</TargetID>\r\n");
broadcastXml.append("</Notify>\r\n"); broadcastXml.append("</Notify>\r\n");
Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent);
} }
@ -594,6 +725,7 @@ public class SIPCommander implements ISIPCommander {
cmdXml.append("</Control>\r\n"); cmdXml.append("</Control>\r\n");
Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request);
} }
@ -621,6 +753,8 @@ public class SIPCommander implements ISIPCommander {
} }
cmdXml.append("</Control>\r\n"); cmdXml.append("</Control>\r\n");
Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent);
} }
@ -732,6 +866,8 @@ public class SIPCommander implements ISIPCommander {
cmdXml.append("</HomePosition>\r\n"); cmdXml.append("</HomePosition>\r\n");
cmdXml.append("</Control>\r\n"); cmdXml.append("</Control>\r\n");
Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent);
} }
@ -816,6 +952,8 @@ public class SIPCommander implements ISIPCommander {
catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n"); catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
catalogXml.append("</Query>\r\n"); catalogXml.append("</Query>\r\n");
Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
@ -1178,8 +1316,6 @@ public class SIPCommander implements ISIPCommander {
/** /**
* 回放暂停 * 回放暂停
*/ */

View File

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

View File

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

View File

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

View File

@ -6,9 +6,12 @@ import com.genersoft.iot.vmp.common.InviteSessionType;
import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
import com.genersoft.iot.vmp.gb28181.bean.*; 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.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor; import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
import com.genersoft.iot.vmp.media.zlm.IStreamSendManager; import com.genersoft.iot.vmp.media.zlm.IStreamSendManager;
@ -52,6 +55,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
@Autowired @Autowired
private ICommonGbChannelService commonGbChannelService; private ICommonGbChannelService commonGbChannelService;
@Autowired
private ISIPCommanderForPlatform commanderForPlatform;
@Autowired @Autowired
private IRedisCatchStorage redisCatchStorage; private IRedisCatchStorage redisCatchStorage;
@ -64,6 +70,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
@Autowired @Autowired
private IDeviceService deviceService; private IDeviceService deviceService;
@Autowired
private AudioBroadcastManager audioBroadcastManager;
@Autowired @Autowired
private IDeviceChannelService channelService; private IDeviceChannelService channelService;
@ -82,6 +91,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
@Autowired @Autowired
private VideoStreamSessionManager streamSession; private VideoStreamSessionManager streamSession;
@Autowired
private IPlayService playService;
@Autowired @Autowired
private UserSetting userSetting; private UserSetting userSetting;
@ -117,7 +129,7 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
param.put("app",sendRtpItem.getApp()); param.put("app",sendRtpItem.getApp());
param.put("stream",streamId); param.put("stream",streamId);
param.put("ssrc",sendRtpItem.getSsrc()); param.put("ssrc",sendRtpItem.getSsrc());
logger.info("[收到bye] 停止向上级推流:{}", streamId); logger.info("[收到bye] 停止推流:{}", streamId);
MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
zlmServerFactory.stopSendRtpStream(mediaInfo, param); zlmServerFactory.stopSendRtpStream(mediaInfo, param);
streamSendManager.remove(sendRtpItem); streamSendManager.remove(sendRtpItem);
@ -138,10 +150,35 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
messageForPushChannel.setPlatFormIndex(platform.getId()); messageForPushChannel.setPlatFormIndex(platform.getId());
redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel); redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel);
}else { }else {
logger.info("[上级平台停止观看] 未找到平台{}的信息发送redis消息失败", sendRtpItem.getDestId()); logger.info("[上级平台停止观看] 未找到平台{}的信息发送redis消息失败", sendRtpItem.getPlatformId());
}
}
AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
if (audioBroadcastCatch != null && audioBroadcastCatch.getSipTransactionInfo().getCallId().equals(callIdHeader.getCallId())) {
// 来自上级平台的停止对讲
logger.info("[停止对讲] 来自上级,平台:{}, 通道:{}", sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
audioBroadcastManager.del(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
}
int totalReaderCount = zlmServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId);
if (totalReaderCount <= 0) {
logger.info("[收到bye] {} 无其它观看者,通知设备停止推流", streamId);
if (sendRtpItem.getPlayType().equals(InviteStreamType.PLAY)) {
Device device = deviceService.getDevice(sendRtpItem.getDeviceId());
if (device == null) {
logger.info("[收到bye] {} 通知设备停止推流时未找到设备信息", streamId);
}
try {
logger.info("[停止点播] {}/{}", sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
cmder.streamByeCmd(device, sendRtpItem.getChannelId(), streamId, null);
} catch (InvalidArgumentException | ParseException | SipException |
SsrcTransactionNotFoundException e) {
logger.error("[收到bye] {} 无其它观看者,通知设备停止推流, 发送BYE失败 {}",streamId, e.getMessage());
}
}
} }
} }
}else {
// 可能是设备发送的停止 // 可能是设备发送的停止
SsrcTransaction ssrcTransaction = streamSession.getSsrcTransactionByCallId(callIdHeader.getCallId()); SsrcTransaction ssrcTransaction = streamSession.getSsrcTransactionByCallId(callIdHeader.getCallId());
@ -150,6 +187,23 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
} }
logger.info("[收到bye] 来自设备:{}, 通道已停止推流: {}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId()); logger.info("[收到bye] 来自设备:{}, 通道已停止推流: {}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
ParentPlatform platform = platformService.queryPlatformByServerGBId(ssrcTransaction.getDeviceId());
if (platform != null ) {
if (ssrcTransaction.getType().equals(InviteSessionType.BROADCAST)) {
logger.info("[收到bye] 上级停止语音对讲,来自:{}, 通道已停止推流: {}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
DeviceChannel channel = storager.queryChannelInParentPlatform(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
if (channel == null) {
logger.info("[收到bye] 未找到通道,设备:{} 通道:{}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
return;
}
String mediaServerId = ssrcTransaction.getMediaServerId();
platformService.stopBroadcast(platform, channel, ssrcTransaction.getStream(), false,
mediaServerService.getOne(mediaServerId));
playService.stopAudioBroadcast(channel.getDeviceId(), channel.getChannelId());
}
}else {
Device device = deviceService.getDevice(ssrcTransaction.getDeviceId()); Device device = deviceService.getDevice(ssrcTransaction.getDeviceId());
if (device == null) { if (device == null) {
logger.info("[收到bye] 未找到设备:{} ", ssrcTransaction.getDeviceId()); logger.info("[收到bye] 未找到设备:{} ", ssrcTransaction.getDeviceId());
@ -174,6 +228,19 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransaction.getSsrc()); mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransaction.getSsrc());
} }
streamSession.removeByCallId(device.getDeviceId(), channel.getChannelId(), ssrcTransaction.getCallId()); streamSession.removeByCallId(device.getDeviceId(), channel.getChannelId(), ssrcTransaction.getCallId());
if (ssrcTransaction.getType() == InviteSessionType.BROADCAST) {
// 查找来源的对讲设备发送停止
Device sourceDevice = storager.queryVideoDeviceByPlatformIdAndChannelId(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
if (sourceDevice != null) {
playService.stopAudioBroadcast(sourceDevice.getDeviceId(), channel.getChannelId());
}
}
AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(ssrcTransaction.getDeviceId(), channel.getChannelId());
if (audioBroadcastCatch != null) {
// 来自上级平台的停止对讲
logger.info("[停止对讲] 来自上级,平台:{}, 通道:{}", ssrcTransaction.getDeviceId(), channel.getChannelId());
audioBroadcastManager.del(ssrcTransaction.getDeviceId(), channel.getChannelId());
}
} }
} }
} }

View File

@ -3,12 +3,20 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.genersoft.iot.vmp.common.CommonGbChannel; import com.genersoft.iot.vmp.common.CommonGbChannel;
import com.genersoft.iot.vmp.common.InviteSessionType;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.*; 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.SSRCFactory;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor; import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; 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.SipUtils;
@ -66,7 +74,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
private final String method = "INVITE"; private final String method = "INVITE";
@Autowired @Autowired
private SIPCommanderFroPlatform cmderFroPlatform; private ISIPCommanderForPlatform cmderFroPlatform;
@Autowired @Autowired
private IVideoManagerStorage storager; private IVideoManagerStorage storager;
@ -76,6 +84,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
@Autowired @Autowired
private IStreamPushService streamPushService; private IStreamPushService streamPushService;
@Autowired @Autowired
private IStreamProxyService streamProxyService; private IStreamProxyService streamProxyService;
@ -97,6 +106,9 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
@Autowired @Autowired
private Map<String, IResourceService> resourceServiceMap; private Map<String, IResourceService> resourceServiceMap;
@Autowired
private AudioBroadcastManager audioBroadcastManager;
@Autowired @Autowired
private ZLMServerFactory zlmServerFactory; private ZLMServerFactory zlmServerFactory;
@ -115,9 +127,16 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
@Autowired @Autowired
private ZLMMediaListManager mediaListManager; private ZLMMediaListManager mediaListManager;
@Autowired
private SipConfig config;
@Autowired @Autowired
private RedisGbPlayMsgListener redisGbPlayMsgListener; private RedisGbPlayMsgListener redisGbPlayMsgListener;
@Autowired
private VideoStreamSessionManager streamSession;
@Override @Override
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
@ -168,7 +187,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
// 查询请求是否来自上级平台\设备 // 查询请求是否来自上级平台\设备
ParentPlatform platform = storager.queryParentPlatByServerGBId(requesterId); ParentPlatform platform = storager.queryParentPlatByServerGBId(requesterId);
if (platform == null) { if (platform == null) {
inviteFromDeviceHandle(request, requesterId); inviteFromDeviceHandle(request, requesterId, channelId);
} else { } else {
// 查询平台下是否有该通道 // 查询平台下是否有该通道
@ -465,6 +484,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId); mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
} }
} }
/** /**
* 通知流上线 * 通知流上线
*/ */
@ -666,8 +686,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
content.append("t=0 0\r\n"); content.append("t=0 0\r\n");
// 非严格模式端口不统一, 增加兼容性修改为一个不为0的端口 // 非严格模式端口不统一, 增加兼容性修改为一个不为0的端口
int localPort = sendRtpItem.getLocalPort(); int localPort = sendRtpItem.getLocalPort();
if(localPort == 0) if (localPort == 0) {
{
localPort = new Random().nextInt(65535) + 1; localPort = new Random().nextInt(65535) + 1;
} }
content.append("m=video " + localPort + " RTP/AVP 96\r\n"); content.append("m=video " + localPort + " RTP/AVP 96\r\n");
@ -686,39 +705,75 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
try { try {
return responseSdpAck(request, content.toString(), platform); return responseSdpAck(request, content.toString(), platform);
} catch (SipException e) { } catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("未处理的异常 ", e);
} catch (InvalidArgumentException e) {
logger.error("未处理的异常 ", e);
} catch (ParseException e) {
logger.error("未处理的异常 ", e); logger.error("未处理的异常 ", e);
} }
return null; return null;
} }
public void inviteFromDeviceHandle(SIPRequest request, String requesterId) { public void inviteFromDeviceHandle(SIPRequest request, String requesterId, String channelId) {
String realChannelId = null;
// 非上级平台请求查询是否设备请求通常为接收语音广播的设备 // 非上级平台请求查询是否设备请求通常为接收语音广播的设备
Device device = redisCatchStorage.getDevice(requesterId); Device device = redisCatchStorage.getDevice(requesterId);
// 判断requesterId是设备还是通道
if (device == null) {
device = storager.queryVideoDeviceByChannelId(requesterId);
realChannelId = requesterId;
}else {
realChannelId = channelId;
}
if (device == null) {
// 检查channelID是否可用
device = redisCatchStorage.getDevice(channelId);
if (device == null) {
device = storager.queryVideoDeviceByChannelId(channelId);
realChannelId = channelId;
}
}
if (device == null) {
logger.warn("来自设备的Invite请求无法从请求信息中确定所属设备已忽略requesterId {}/{}", requesterId, channelId);
try {
responseAck(request, Response.FORBIDDEN);
} catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("[命令发送失败] 来自设备的Invite请求无法从请求信息中确定所属设备 FORBIDDEN: {}", e.getMessage());
}
return;
}
AudioBroadcastCatch broadcastCatch = audioBroadcastManager.get(device.getDeviceId(), realChannelId);
if (broadcastCatch == null) {
logger.warn("来自设备的Invite请求非语音广播已忽略requesterId {}/{}", requesterId, channelId);
try {
responseAck(request, Response.FORBIDDEN);
} catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("[命令发送失败] 来自设备的Invite请求非语音广播 FORBIDDEN: {}", e.getMessage());
}
return;
}
if (device != null) { if (device != null) {
logger.info("收到设备" + requesterId + "的语音广播Invite请求"); logger.info("收到设备" + requesterId + "的语音广播Invite请求");
String key = VideoManagerConstants.BROADCAST_WAITE_INVITE + device.getDeviceId() + broadcastCatch.getChannelId();
dynamicTask.stop(key);
try { try {
responseAck(request, Response.TRYING); responseAck(request, Response.TRYING);
} catch (SipException | InvalidArgumentException | ParseException e) { } catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("[命令发送失败] invite BAD_REQUEST: {}", e.getMessage()); logger.error("[命令发送失败] invite BAD_REQUEST: {}", e.getMessage());
playService.stopAudioBroadcast(device.getDeviceId(), broadcastCatch.getChannelId());
return;
} }
String contentString = new String(request.getRawContent()); String contentString = new String(request.getRawContent());
// jainSip不支持y=字段 移除移除以解析
String ssrc = "0000000404";
try { try {
Gb28181Sdp gb28181Sdp = SipUtils.parseSDP(contentString); Gb28181Sdp gb28181Sdp = SipUtils.parseSDP(contentString);
SessionDescription sdp = gb28181Sdp.getBaseSdb(); SessionDescription sdp = gb28181Sdp.getBaseSdb();
// 获取支持的格式 // 获取支持的格式
Vector mediaDescriptions = sdp.getMediaDescriptions(true); Vector mediaDescriptions = sdp.getMediaDescriptions(true);
// 查看是否支持PS 负载96 // 查看是否支持PS 负载96
int port = -1; int port = -1;
//boolean recvonly = false;
boolean mediaTransmissionTCP = false; boolean mediaTransmissionTCP = false;
Boolean tcpActive = null; Boolean tcpActive = null;
for (int i = 0; i < mediaDescriptions.size(); i++) { for (int i = 0; i < mediaDescriptions.size(); i++) {
@ -750,26 +805,147 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
try { try {
responseAck(request, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式发415 responseAck(request, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式发415
} catch (SipException | InvalidArgumentException | ParseException e) { } catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("[命令发送失败] invite 不支持的媒体格式返回415 {}", e.getMessage()); logger.error("[命令发送失败] invite 不支持的媒体格式: {}", e.getMessage());
playService.stopAudioBroadcast(device.getDeviceId(), broadcastCatch.getChannelId());
return;
} }
return; return;
} }
String username = sdp.getOrigin().getUsername(); String addressStr = sdp.getOrigin().getAddress();
String addressStr = sdp.getConnection().getAddress(); logger.info("设备{}请求语音流,地址:{}:{}ssrc{}, {}", requesterId, addressStr, port, gb28181Sdp.getSsrc(),
logger.info("设备{}请求语音流,地址:{}:{}ssrc{}", username, addressStr, port, ssrc); mediaTransmissionTCP ? (tcpActive ? "TCP主动" : "TCP被动") : "UDP");
} catch (SdpException e) {
logger.error("[SDP解析异常]", e); MediaServerItem mediaServerItem = broadcastCatch.getMediaServerItem();
if (mediaServerItem == null) {
logger.warn("未找到语音喊话使用的zlm");
try {
responseAck(request, Response.BUSY_HERE);
} catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("[命令发送失败] invite 未找到可用的zlm: {}", e.getMessage());
playService.stopAudioBroadcast(device.getDeviceId(), broadcastCatch.getChannelId());
}
return;
}
logger.info("设备{}请求语音流, 收流地址:{}:{}ssrc{}, {}, 对讲方式:{}", requesterId, addressStr, port, gb28181Sdp.getSsrc(),
mediaTransmissionTCP ? (tcpActive ? "TCP主动" : "TCP被动") : "UDP", sdp.getSessionName().getValue());
CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME);
SendRtpItem sendRtpItem = zlmServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, gb28181Sdp.getSsrc(), requesterId,
device.getDeviceId(), broadcastCatch.getChannelId(),
mediaTransmissionTCP, false);
if (sendRtpItem == null) {
logger.warn("服务器端口资源不足");
try {
responseAck(request, Response.BUSY_HERE);
} catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("[命令发送失败] invite 服务器端口资源不足: {}", e.getMessage());
playService.stopAudioBroadcast(device.getDeviceId(), broadcastCatch.getChannelId());
return;
}
return;
} }
sendRtpItem.setPlayType(InviteStreamType.BROADCAST);
sendRtpItem.setCallId(callIdHeader.getCallId());
sendRtpItem.setPlatformId(requesterId);
sendRtpItem.setStatus(1);
sendRtpItem.setApp(broadcastCatch.getApp());
sendRtpItem.setStream(broadcastCatch.getStream());
sendRtpItem.setPt(8);
sendRtpItem.setUsePs(false);
sendRtpItem.setRtcp(false);
sendRtpItem.setOnlyAudio(true);
sendRtpItem.setTcp(mediaTransmissionTCP);
if (tcpActive != null) {
sendRtpItem.setTcpActive(tcpActive);
}
redisCatchStorage.updateSendRTPSever(sendRtpItem);
Boolean streamReady = zlmServerFactory.isStreamReady(mediaServerItem, broadcastCatch.getApp(), broadcastCatch.getStream());
if (streamReady) {
sendOk(device, sendRtpItem, sdp, request, mediaServerItem, mediaTransmissionTCP, gb28181Sdp.getSsrc());
} else {
logger.warn("[语音通话] 未发现待推送的流,app={},stream={}", broadcastCatch.getApp(), broadcastCatch.getStream());
try {
responseAck(request, Response.GONE);
} catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("[命令发送失败] 语音通话 回复410失败 {}", e.getMessage());
return;
}
playService.stopAudioBroadcast(device.getDeviceId(), broadcastCatch.getChannelId());
}
} catch (SdpException e) {
logger.error("[SDP解析异常]", e);
playService.stopAudioBroadcast(device.getDeviceId(), broadcastCatch.getChannelId());
}
} else { } else {
logger.warn("来自无效设备/平台的请求"); logger.warn("来自无效设备/平台的请求");
try { try {
responseAck(request, Response.BAD_REQUEST);; // 不支持的格式发415 responseAck(request, Response.BAD_REQUEST);
; // 不支持的格式发415
} catch (SipException | InvalidArgumentException | ParseException e) { } catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("[命令发送失败] invite 来自无效设备/平台的请求, {}", e.getMessage()); logger.error("[命令发送失败] invite 来自无效设备/平台的请求, {}", e.getMessage());
} }
} }
} }
SIPResponse sendOk(Device device, SendRtpItem sendRtpItem, SessionDescription sdp, SIPRequest request, MediaServerItem mediaServerItem, boolean mediaTransmissionTCP, String ssrc) {
SIPResponse sipResponse = null;
try {
sendRtpItem.setStatus(2);
redisCatchStorage.updateSendRTPSever(sendRtpItem);
StringBuffer content = new StringBuffer(200);
content.append("v=0\r\n");
content.append("o=" + config.getId() + " " + sdp.getOrigin().getSessionId() + " " + sdp.getOrigin().getSessionVersion() + " IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
content.append("s=Play\r\n");
content.append("c=IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
content.append("t=0 0\r\n");
if (mediaTransmissionTCP) {
content.append("m=audio " + sendRtpItem.getLocalPort() + " TCP/RTP/AVP 8\r\n");
} else {
content.append("m=audio " + sendRtpItem.getLocalPort() + " RTP/AVP 8\r\n");
}
content.append("a=rtpmap:8 PCMA/8000/1\r\n");
content.append("a=sendonly\r\n");
if (sendRtpItem.isTcp()) {
content.append("a=connection:new\r\n");
if (!sendRtpItem.isTcpActive()) {
content.append("a=setup:active\r\n");
} else {
content.append("a=setup:passive\r\n");
}
}
content.append("y=" + ssrc + "\r\n");
content.append("f=v/////a/1/8/1\r\n");
ParentPlatform parentPlatform = new ParentPlatform();
parentPlatform.setServerIP(device.getIp());
parentPlatform.setServerPort(device.getPort());
parentPlatform.setServerGBId(device.getDeviceId());
sipResponse = responseSdpAck(request, content.toString(), parentPlatform);
AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(device.getDeviceId(), sendRtpItem.getChannelId());
audioBroadcastCatch.setStatus(AudioBroadcastCatchStatus.Ok);
audioBroadcastCatch.setSipTransactionInfoByRequset(sipResponse);
audioBroadcastManager.update(audioBroadcastCatch);
streamSession.put(device.getDeviceId(), sendRtpItem.getChannelId(), request.getCallIdHeader().getCallId(), sendRtpItem.getStream(), sendRtpItem.getSsrc(), sendRtpItem.getMediaServerId(), sipResponse, InviteSessionType.BROADCAST);
// 开启发流大华在收到200OK后就会开始建立连接
if (!device.isBroadcastPushAfterAck()) {
logger.info("[语音喊话] 回复200OK后发现 BroadcastPushAfterAck为False现在开始推流");
playService.startPushStream(sendRtpItem, sipResponse, parentPlatform, request.getCallIdHeader());
}
} catch (SipException | InvalidArgumentException | ParseException | SdpParseException e) {
logger.error("[命令发送失败] 语音喊话 回复200OKSDP: {}", e.getMessage());
}
return sipResponse;
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -77,6 +77,50 @@ public class XmlUtil {
return null == e ? null : e.getText().trim(); return null == e ? null : e.getText().trim();
} }
/**
* 获取element对象的text的值
*
* @param em 节点的对象
* @param tag 节点的tag
* @return 节点
*/
public static Double getDouble(Element em, String tag) {
if (null == em) {
return null;
}
Element e = em.element(tag);
if (null == e) {
return null;
}
String text = e.getText().trim();
if (ObjectUtils.isEmpty(text) || !NumberUtils.isParsable(text)) {
return null;
}
return Double.parseDouble(text);
}
/**
* 获取element对象的text的值
*
* @param em 节点的对象
* @param tag 节点的tag
* @return 节点
*/
public static Integer getInteger(Element em, String tag) {
if (null == em) {
return null;
}
Element e = em.element(tag);
if (null == e) {
return null;
}
String text = e.getText().trim();
if (ObjectUtils.isEmpty(text) || !NumberUtils.isParsable(text)) {
return null;
}
return Integer.parseInt(text);
}
/** /**
* 递归解析xml节点适用于 多节点数据 * 递归解析xml节点适用于 多节点数据
* *

View File

@ -10,13 +10,20 @@ import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.event.EventPublisher; 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.SSRCFactory;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; 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.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
import com.genersoft.iot.vmp.media.zlm.dto.*; import com.genersoft.iot.vmp.media.zlm.dto.*;
import com.genersoft.iot.vmp.media.zlm.dto.HookType;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
import com.genersoft.iot.vmp.media.zlm.dto.hook.*; import com.genersoft.iot.vmp.media.zlm.dto.hook.*;
import com.genersoft.iot.vmp.service.*; import com.genersoft.iot.vmp.service.*;
import com.genersoft.iot.vmp.service.bean.MessageForPushChannel; import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
@ -62,7 +69,13 @@ public class ZLMHttpHookListener {
private SIPCommander cmder; private SIPCommander cmder;
@Autowired @Autowired
private SIPCommanderFroPlatform commanderFroPlatform; private ISIPCommanderForPlatform commanderFroPlatform;
@Autowired
private AudioBroadcastManager audioBroadcastManager;
@Autowired
private ZLMServerFactory zlmServerFactory;
@Autowired @Autowired
private IPlayService playService; private IPlayService playService;
@ -308,8 +321,16 @@ public class ZLMHttpHookListener {
result.setModify_stamp(2); result.setModify_stamp(2);
} }
} }
// 如果是talk对讲则默认获取声音
if (ssrcTransactionForAll.get(0).getType() == InviteSessionType.TALK) {
result.setEnable_audio(true);
} }
} }
} else if (param.getApp().equals("broadcast")) {
result.setEnable_audio(true);
} else if (param.getApp().equals("talk")) {
result.setEnable_audio(true);
}
if (param.getApp().equalsIgnoreCase("rtp")) { if (param.getApp().equalsIgnoreCase("rtp")) {
String receiveKey = VideoManagerConstants.WVP_OTHER_RECEIVE_RTP_INFO + userSetting.getServerId() + "_" + param.getStream(); String receiveKey = VideoManagerConstants.WVP_OTHER_RECEIVE_RTP_INFO + userSetting.getServerId() + "_" + param.getStream();
OtherRtpSendInfo otherRtpSendInfo = (OtherRtpSendInfo) redisTemplate.opsForValue().get(receiveKey); OtherRtpSendInfo otherRtpSendInfo = (OtherRtpSendInfo) redisTemplate.opsForValue().get(receiveKey);
@ -340,7 +361,6 @@ public class ZLMHttpHookListener {
OriginType.values()[param.getOriginType()].getType(), param.getApp(), param.getStream()); OriginType.values()[param.getOriginType()].getType(), param.getApp(), param.getStream());
} }
JSONObject json = (JSONObject) JSON.toJSON(param); JSONObject json = (JSONObject) JSON.toJSON(param);
taskExecutor.execute(() -> { taskExecutor.execute(() -> {
// 发送hook订阅通知 // 发送hook订阅通知
@ -370,15 +390,17 @@ public class ZLMHttpHookListener {
redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo); redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo);
} }
} }
if ("rtsp".equals(param.getSchema())) { if ("rtsp".equals(param.getSchema())) {
// 更新流媒体负载信息 logger.info("流变化:注册->{}, app->{}, stream->{}", param.isRegist(), param.getApp(), param.getStream());
if (param.isRegist()) { if (param.isRegist()) {
mediaServerService.addCount(param.getMediaServerId()); mediaServerService.addCount(param.getMediaServerId());
} else { } else {
mediaServerService.removeCount(param.getMediaServerId()); mediaServerService.removeCount(param.getMediaServerId());
} }
int updateStatusResult = streamProxyService.updateStatus(param.isRegist(), param.getApp(), param.getStream());
if (updateStatusResult > 0) {
}
if ("rtp".equals(param.getApp())) { if ("rtp".equals(param.getApp())) {
if (!param.isRegist()) { if (!param.isRegist()) {
InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, param.getStream()); InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, param.getStream());
@ -388,6 +410,63 @@ public class ZLMHttpHookListener {
} }
} }
} 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);
}
} 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);
}
// 开启语音对讲通道
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 { } else {
String type; String type;
if (param.getOriginType() == 0) { if (param.getOriginType() == 0) {
@ -458,10 +537,19 @@ public class ZLMHttpHookListener {
streamSendManager.remove(sendRtpItem); streamSendManager.remove(sendRtpItem);
} else { } else {
cmder.streamByeCmd(device, sendRtpItem.getChannelId(), param.getStream(), sendRtpItem.getCallId()); cmder.streamByeCmd(device, sendRtpItem.getChannelId(), param.getStream(), sendRtpItem.getCallId());
if (sendRtpItem.getPlayType().equals(InviteStreamType.BROADCAST)
|| sendRtpItem.getPlayType().equals(InviteStreamType.TALK)) {
AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
if (audioBroadcastCatch != null) {
// 来自上级平台的停止对讲
logger.info("[停止对讲] 来自上级,平台:{}, 通道:{}", sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
audioBroadcastManager.del(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
}
}
} }
} catch (SipException | InvalidArgumentException | ParseException | } catch (SipException | InvalidArgumentException | ParseException |
SsrcTransactionNotFoundException e) { SsrcTransactionNotFoundException e) {
logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); logger.error("[命令发送失败] 发送BYE: {}", e.getMessage());
} }
} }
} }
@ -469,7 +557,6 @@ public class ZLMHttpHookListener {
} }
} }
}); });
return HookResult.SUCCESS(); return HookResult.SUCCESS();
} }
@ -541,6 +628,13 @@ public class ZLMHttpHookListener {
storager.stopPlay(inviteInfo.getDeviceId(), inviteInfo.getChannelId()); storager.stopPlay(inviteInfo.getDeviceId(), inviteInfo.getChannelId());
return ret; return ret;
} }
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, param.getStream(), null);
if (sendRtpItem != null && "talk".equals(sendRtpItem.getApp())) {
ret.put("close", false);
return ret;
}
} else if ("talk".equals(param.getApp()) || "broadcast".equals(param.getApp())) {
ret.put("close", false);
} else { } else {
// 非国标流 推流/拉流代理 // 非国标流 推流/拉流代理
// 拉流代理 // 拉流代理
@ -662,7 +756,7 @@ public class ZLMHttpHookListener {
if (!exist) { if (!exist) {
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaInfo, param.getStream(), null, SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaInfo, param.getStream(), null,
device.isSsrcCheck(), true, 0, false, device.getStreamModeForParam()); device.isSsrcCheck(), true, 0, false, false, device.getStreamModeForParam());
playService.playBack(mediaInfo, ssrcInfo, deviceId, channelId, startTime, endTime, (code, message, data) -> { playService.playBack(mediaInfo, ssrcInfo, deviceId, channelId, startTime, endTime, (code, message, data) -> {
msg.setData(new HookResult(code, message)); msg.setData(new HookResult(code, message));
resultHolder.invokeResult(msg); resultHolder.invokeResult(msg);
@ -747,7 +841,8 @@ public class ZLMHttpHookListener {
*/ */
@ResponseBody @ResponseBody
@PostMapping(value = "/on_rtp_server_timeout", produces = "application/json;charset=UTF-8") @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()); logger.info("[ZLM HOOK] rtpServer收流超时{}->{}({})", param.getMediaServerId(), param.getStream_id(), param.getSsrc());
taskExecutor.execute(() -> { taskExecutor.execute(() -> {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -53,4 +53,5 @@ public interface IMediaService {
boolean closeStream(MediaServerItem mediaInfo, String app, String stream); boolean closeStream(MediaServerItem mediaInfo, String app, String stream);
String getStreamType(String app, String stream); String getStreamType(String app, String stream);
StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, Object tracks, String addr, String callId, boolean isPlay);
} }

View File

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

View File

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

View File

@ -6,6 +6,7 @@ import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask; import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask;
import com.genersoft.iot.vmp.gb28181.task.impl.CatalogSubscribeTask; import com.genersoft.iot.vmp.gb28181.task.impl.CatalogSubscribeTask;
@ -15,6 +16,12 @@ import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler;
import com.genersoft.iot.vmp.service.*; import com.genersoft.iot.vmp.service.*;
import com.genersoft.iot.vmp.service.bean.Group; import com.genersoft.iot.vmp.service.bean.Group;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IDeviceChannelService;
import com.genersoft.iot.vmp.service.IDeviceService;
import com.genersoft.iot.vmp.service.IInviteStreamService;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper; import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper;
import com.genersoft.iot.vmp.storager.dao.DeviceMapper; import com.genersoft.iot.vmp.storager.dao.DeviceMapper;
@ -37,9 +44,7 @@ import javax.sip.InvalidArgumentException;
import javax.sip.SipException; import javax.sip.SipException;
import java.text.ParseException; import java.text.ParseException;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList; import java.util.*;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
@ -99,6 +104,12 @@ public class DeviceServiceImpl implements IDeviceService {
@Autowired @Autowired
private ICommonGbChannelService commonGbChannelService; private ICommonGbChannelService commonGbChannelService;
@Autowired
private AudioBroadcastManager audioBroadcastManager;
@Autowired
private ZLMRESTfulUtils zlmresTfulUtils;
@Override @Override
public void online(Device device, SipTransactionInfo sipTransactionInfo) { public void online(Device device, SipTransactionInfo sipTransactionInfo) {
logger.info("[设备上线] deviceId{}->{}:{}", device.getDeviceId(), device.getIp(), device.getPort()); logger.info("[设备上线] deviceId{}->{}:{}", device.getDeviceId(), device.getIp(), device.getPort());
@ -238,6 +249,25 @@ public class DeviceServiceImpl implements IDeviceService {
// 移除订阅 // 移除订阅
removeCatalogSubscribe(device, null); removeCatalogSubscribe(device, null);
removeMobilePositionSubscribe(device, null); removeMobilePositionSubscribe(device, null);
List<AudioBroadcastCatch> audioBroadcastCatches = audioBroadcastManager.get(deviceId);
if (audioBroadcastCatches.size() > 0) {
for (AudioBroadcastCatch audioBroadcastCatch : audioBroadcastCatches) {
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(deviceId, audioBroadcastCatch.getChannelId(), null, null);
if (sendRtpItem != null) {
redisCatchStorage.deleteSendRTPServer(deviceId, sendRtpItem.getChannelId(), null, null);
MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
Map<String, Object> param = new HashMap<>();
param.put("vhost", "__defaultVhost__");
param.put("app", sendRtpItem.getApp());
param.put("stream", sendRtpItem.getStream());
zlmresTfulUtils.stopSendRtp(mediaInfo, param);
}
audioBroadcastManager.del(deviceId, audioBroadcastCatch.getChannelId());
}
}
} }
@Override @Override

View File

@ -122,6 +122,8 @@ public class MediaServerServiceImpl implements IMediaServerService {
/** /**
* 初始化 * 初始化
*/ */
@ -148,7 +150,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
@Override @Override
public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String presetSsrc, boolean ssrcCheck, public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String presetSsrc, boolean ssrcCheck,
boolean isPlayback, Integer port, Boolean reUsePort, Integer tcpMode) { boolean isPlayback, Integer port, Boolean onlyAuto, Boolean reUsePort, Integer tcpMode) {
if (mediaServerItem == null || mediaServerItem.getId() == null) { if (mediaServerItem == null || mediaServerItem.getId() == null) {
logger.info("[openRTPServer] 失败, mediaServerItem == null || mediaServerItem.getId() == null"); logger.info("[openRTPServer] 失败, mediaServerItem == null || mediaServerItem.getId() == null");
return null; return null;
@ -174,13 +176,19 @@ public class MediaServerServiceImpl implements IMediaServerService {
} }
int rtpServerPort; int rtpServerPort;
if (mediaServerItem.isRtpEnable()) { if (mediaServerItem.isRtpEnable()) {
rtpServerPort = zlmServerFactory.createRTPServer(mediaServerItem, streamId, ssrcCheck ? Long.parseLong(ssrc) : 0, port, reUsePort, tcpMode); rtpServerPort = zlmServerFactory.createRTPServer(mediaServerItem, streamId, ssrcCheck ? Long.parseLong(ssrc) : 0, port, onlyAuto, reUsePort, tcpMode);
} else { } else {
rtpServerPort = mediaServerItem.getRtpProxyPort(); rtpServerPort = mediaServerItem.getRtpProxyPort();
} }
return new SSRCInfo(rtpServerPort, ssrc, streamId); return new SSRCInfo(rtpServerPort, ssrc, streamId);
} }
@Override
public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck, boolean isPlayback, Integer port, Boolean onlyAuto) {
return openRTPServer(mediaServerItem, streamId, ssrc, ssrcCheck, isPlayback, port, onlyAuto, null, 0);
}
@Override @Override
public void closeRTPServer(MediaServerItem mediaServerItem, String streamId) { public void closeRTPServer(MediaServerItem mediaServerItem, String streamId) {
if (mediaServerItem == null) { if (mediaServerItem == null) {
@ -309,6 +317,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
return JsonUtil.redisJsonToObject(redisTemplate, key, MediaServerItem.class); return JsonUtil.redisJsonToObject(redisTemplate, key, MediaServerItem.class);
} }
@Override @Override
public MediaServerItem getDefaultMediaServer() { public MediaServerItem getDefaultMediaServer() {
return mediaServerMapper.queryDefault(); return mediaServerMapper.queryDefault();

View File

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

View File

@ -238,8 +238,6 @@ public class PlatformChannelServiceImpl implements IPlatformChannelService {
public CommonGbChannel queryChannelByPlatformIdAndChannelDeviceId(Integer platformId, String channelId) { public CommonGbChannel queryChannelByPlatformIdAndChannelDeviceId(Integer platformId, String channelId) {
return platformChannelMapper.queryChannelByPlatformIdAndChannelDeviceId(platformId, channelId); return platformChannelMapper.queryChannelByPlatformIdAndChannelDeviceId(platformId, channelId);
} }
@Override
public List<CommonGbChannel> queryCommonGbChannellList(Integer platformId) { public List<CommonGbChannel> queryCommonGbChannellList(Integer platformId) {
return platformChannelMapper.queryCommonGbChannellList(platformId); return platformChannelMapper.queryCommonGbChannellList(platformId);
} }

View File

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

View File

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

View File

@ -91,7 +91,7 @@ public class RedisPushStreamCloseResponseListener implements MessageListener {
} }
if (push.isSelf()) { if (push.isSelf()) {
// 停止向上级推流 // 停止向上级推流
String streamId = sendRtpItem.getStreamId(); String streamId = sendRtpItem.getStream();
Map<String, Object> param = new HashMap<>(); Map<String, Object> param = new HashMap<>();
param.put("vhost","__defaultVhost__"); param.put("vhost","__defaultVhost__");
param.put("app",sendRtpItem.getApp()); param.put("app",sendRtpItem.getApp());

View File

@ -44,6 +44,7 @@ public interface DeviceMapper {
"geo_coord_sys," + "geo_coord_sys," +
"on_line," + "on_line," +
"media_server_id," + "media_server_id," +
"broadcast_push_after_ack," +
"(SELECT count(0) FROM wvp_device_channel WHERE device_id=wvp_device.device_id) as channel_count "+ "(SELECT count(0) FROM wvp_device_channel WHERE device_id=wvp_device.device_id) as channel_count "+
" FROM wvp_device WHERE device_id = #{deviceId}") " FROM wvp_device WHERE device_id = #{deviceId}")
Device getDeviceByDeviceId(String deviceId); Device getDeviceByDeviceId(String deviceId);
@ -74,6 +75,7 @@ public interface DeviceMapper {
"subscribe_cycle_for_alarm,"+ "subscribe_cycle_for_alarm,"+
"ssrc_check,"+ "ssrc_check,"+
"as_message_channel,"+ "as_message_channel,"+
"broadcast_push_after_ack,"+
"geo_coord_sys,"+ "geo_coord_sys,"+
"on_line"+ "on_line"+
") VALUES (" + ") VALUES (" +
@ -102,6 +104,7 @@ public interface DeviceMapper {
"#{subscribeCycleForAlarm}," + "#{subscribeCycleForAlarm}," +
"#{ssrcCheck}," + "#{ssrcCheck}," +
"#{asMessageChannel}," + "#{asMessageChannel}," +
"#{broadcastPushAfterAck}," +
"#{geoCoordSys}," + "#{geoCoordSys}," +
"#{onLine}" + "#{onLine}" +
")") ")")
@ -157,6 +160,7 @@ public interface DeviceMapper {
"subscribe_cycle_for_alarm,"+ "subscribe_cycle_for_alarm,"+
"ssrc_check,"+ "ssrc_check,"+
"as_message_channel,"+ "as_message_channel,"+
"broadcast_push_after_ack,"+
"geo_coord_sys,"+ "geo_coord_sys,"+
"on_line,"+ "on_line,"+
"media_server_id,"+ "media_server_id,"+
@ -197,6 +201,7 @@ public interface DeviceMapper {
"subscribe_cycle_for_alarm,"+ "subscribe_cycle_for_alarm,"+
"ssrc_check,"+ "ssrc_check,"+
"as_message_channel,"+ "as_message_channel,"+
"broadcast_push_after_ack,"+
"geo_coord_sys,"+ "geo_coord_sys,"+
"on_line"+ "on_line"+
" FROM wvp_device WHERE on_line = true") " FROM wvp_device WHERE on_line = true")
@ -227,6 +232,7 @@ public interface DeviceMapper {
"subscribe_cycle_for_alarm,"+ "subscribe_cycle_for_alarm,"+
"ssrc_check,"+ "ssrc_check,"+
"as_message_channel,"+ "as_message_channel,"+
"broadcast_push_after_ack,"+
"geo_coord_sys,"+ "geo_coord_sys,"+
"on_line"+ "on_line"+
" FROM wvp_device WHERE ip = #{host} AND port=#{port}") " FROM wvp_device WHERE ip = #{host} AND port=#{port}")
@ -248,6 +254,7 @@ public interface DeviceMapper {
"<if test=\"subscribeCycleForAlarm != null\">, subscribe_cycle_for_alarm=#{subscribeCycleForAlarm}</if>" + "<if test=\"subscribeCycleForAlarm != null\">, subscribe_cycle_for_alarm=#{subscribeCycleForAlarm}</if>" +
"<if test=\"ssrcCheck != null\">, ssrc_check=#{ssrcCheck}</if>" + "<if test=\"ssrcCheck != null\">, ssrc_check=#{ssrcCheck}</if>" +
"<if test=\"asMessageChannel != null\">, as_message_channel=#{asMessageChannel}</if>" + "<if test=\"asMessageChannel != null\">, as_message_channel=#{asMessageChannel}</if>" +
"<if test=\"broadcastPushAfterAck != null\">, broadcast_push_after_ack=#{broadcastPushAfterAck}</if>" +
"<if test=\"geoCoordSys != null\">, geo_coord_sys=#{geoCoordSys}</if>" + "<if test=\"geoCoordSys != null\">, geo_coord_sys=#{geoCoordSys}</if>" +
"<if test=\"mediaServerId != null\">, media_server_id=#{mediaServerId}</if>" + "<if test=\"mediaServerId != null\">, media_server_id=#{mediaServerId}</if>" +
"WHERE device_id=#{deviceId}"+ "WHERE device_id=#{deviceId}"+
@ -264,6 +271,7 @@ public interface DeviceMapper {
"charset,"+ "charset,"+
"ssrc_check,"+ "ssrc_check,"+
"as_message_channel,"+ "as_message_channel,"+
"broadcastPushAfterAck,"+
"geo_coord_sys,"+ "geo_coord_sys,"+
"on_line,"+ "on_line,"+
"media_server_id"+ "media_server_id"+
@ -277,6 +285,7 @@ public interface DeviceMapper {
"#{charset}," + "#{charset}," +
"#{ssrcCheck}," + "#{ssrcCheck}," +
"#{asMessageChannel}," + "#{asMessageChannel}," +
"#{broadcastPushAfterAck}," +
"#{geoCoordSys}," + "#{geoCoordSys}," +
"#{onLine}," + "#{onLine}," +
"#{mediaServerId}" + "#{mediaServerId}" +

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

View File

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

View File

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

View File

@ -3,20 +3,26 @@
<el-dialog title="视频播放" top="0" :close-on-click-modal="false" :visible.sync="showVideoDialog" @close="close()" v-if="showVideoDialog"> <el-dialog title="视频播放" top="0" :close-on-click-modal="false" :visible.sync="showVideoDialog" @close="close()" v-if="showVideoDialog">
<div style="width: 100%; height: 100%"> <div style="width: 100%; height: 100%">
<el-tabs type="card" :stretch="true" v-model="activePlayer" @tab-click="changePlayer" v-if="Object.keys(this.player).length > 1"> <el-tabs type="card" :stretch="true" v-model="activePlayer" @tab-click="changePlayer"
<!-- <el-tab-pane label="LivePlayer" name="livePlayer">--> v-if="Object.keys(this.player).length > 1">
<!-- <LivePlayer v-if="showVideoDialog" ref="livePlayer" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" :hasaudio="hasAudio" fluent autoplay live></LivePlayer>-->
<!-- </el-tab-pane>-->
<el-tab-pane label="Jessibuca" name="jessibuca"> <el-tab-pane label="Jessibuca" name="jessibuca">
<jessibucaPlayer v-if="activePlayer === 'jessibuca'" ref="jessibuca" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px" :hasAudio="hasAudio" fluent autoplay live ></jessibucaPlayer> <jessibucaPlayer v-if="activePlayer === 'jessibuca'" ref="jessibuca" :visible.sync="showVideoDialog"
:videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px"
:hasAudio="hasAudio" fluent autoplay live></jessibucaPlayer>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="WebRTC" name="webRTC"> <el-tab-pane label="WebRTC" name="webRTC">
<rtc-player v-if="activePlayer === 'webRTC'" ref="webRTC" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px" :hasAudio="hasAudio" fluent autoplay live ></rtc-player> <rtc-player v-if="activePlayer === 'webRTC'" ref="webRTC" :visible.sync="showVideoDialog"
:videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px"
:hasAudio="hasAudio" fluent autoplay live></rtc-player>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="h265web">h265web敬请期待</el-tab-pane> <el-tab-pane label="h265web">h265web敬请期待</el-tab-pane>
</el-tabs> </el-tabs>
<jessibucaPlayer v-if="Object.keys(this.player).length == 1 && this.player.jessibuca" ref="jessibuca" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px" :hasAudio="hasAudio" fluent autoplay live ></jessibucaPlayer> <jessibucaPlayer v-if="Object.keys(this.player).length == 1 && this.player.jessibuca" ref="jessibuca"
<rtc-player v-if="Object.keys(this.player).length == 1 && this.player.webRTC" ref="jessibuca" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px" :hasAudio="hasAudio" fluent autoplay live ></rtc-player> :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError"
height="100px" :hasAudio="hasAudio" fluent autoplay live></jessibucaPlayer>
<rtc-player v-if="Object.keys(this.player).length == 1 && this.player.webRTC" ref="jessibuca"
:visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError"
height="100px" :hasAudio="hasAudio" fluent autoplay live></rtc-player>
</div> </div>
<div id="shared" style="text-align: right; margin-top: 1rem;"> <div id="shared" style="text-align: right; margin-top: 1rem;">
@ -26,7 +32,8 @@
<span style="width: 5rem; line-height: 2.5rem; text-align: right;">播放地址</span> <span style="width: 5rem; line-height: 2.5rem; text-align: right;">播放地址</span>
<el-input v-model="getPlayerShared.sharedUrl" :disabled="true"> <el-input v-model="getPlayerShared.sharedUrl" :disabled="true">
<template slot="append"> <template slot="append">
<i class="cpoy-btn el-icon-document-copy" title="点击拷贝" v-clipboard="getPlayerShared.sharedUrl" @success="$message({type:'success', message:'成功拷贝到粘贴板'})"></i> <i class="cpoy-btn el-icon-document-copy" title="点击拷贝" v-clipboard="getPlayerShared.sharedUrl"
@success="$message({type:'success', message:'成功拷贝到粘贴板'})"></i>
</template> </template>
</el-input> </el-input>
</div> </div>
@ -34,14 +41,17 @@
<span style="width: 5rem; line-height: 2.5rem; text-align: right;">iframe</span> <span style="width: 5rem; line-height: 2.5rem; text-align: right;">iframe</span>
<el-input v-model="getPlayerShared.sharedIframe" :disabled="true"> <el-input v-model="getPlayerShared.sharedIframe" :disabled="true">
<template slot="append"> <template slot="append">
<i class="cpoy-btn el-icon-document-copy" title="点击拷贝" v-clipboard="getPlayerShared.sharedIframe" @success="$message({type:'success', message:'成功拷贝到粘贴板'})"></i> <i class="cpoy-btn el-icon-document-copy" title="点击拷贝" v-clipboard="getPlayerShared.sharedIframe"
@success="$message({type:'success', message:'成功拷贝到粘贴板'})"></i>
</template> </template>
</el-input> </el-input>
</div> </div>
<div style="display: flex; margin-bottom: 0.5rem; height: 2.5rem;"> <div style="display: flex; margin-bottom: 0.5rem; height: 2.5rem;">
<span style="width: 5rem; line-height: 2.5rem; text-align: right;">资源地址</span> <span style="width: 5rem; line-height: 2.5rem; text-align: right;">资源地址</span>
<el-input v-model="getPlayerShared.sharedRtmp" :disabled="true"> <el-input v-model="getPlayerShared.sharedRtmp" :disabled="true">
<el-button slot="append" icon="el-icon-document-copy" title="点击拷贝" v-clipboard="getPlayerShared.sharedRtmp" @success="$message({type:'success', message:'成功拷贝到粘贴板'})"></el-button> <el-button slot="append" icon="el-icon-document-copy" title="点击拷贝"
v-clipboard="getPlayerShared.sharedRtmp"
@success="$message({type:'success', message:'成功拷贝到粘贴板'})"></el-button>
<el-dropdown slot="prepend" v-if="streamInfo" trigger="click" @command="copyUrl"> <el-dropdown slot="prepend" v-if="streamInfo" trigger="click" @command="copyUrl">
<el-button> <el-button>
更多地址<i class="el-icon-arrow-down el-icon--right"></i> 更多地址<i class="el-icon-arrow-down el-icon--right"></i>
@ -165,8 +175,12 @@
<div class="control-round"> <div class="control-round">
<div class="control-round-inner"><i class="fa fa-pause-circle"></i></div> <div class="control-round-inner"><i class="fa fa-pause-circle"></i></div>
</div> </div>
<div style="position: absolute; left: 7.25rem; top: 1.25rem" @mousedown="ptzCamera('zoomin')" @mouseup="ptzCamera('stop')"><i class="el-icon-zoom-in control-zoom-btn" style="font-size: 1.875rem;"></i></div> <div style="position: absolute; left: 7.25rem; top: 1.25rem" @mousedown="ptzCamera('zoomin')"
<div style="position: absolute; left: 7.25rem; top: 3.25rem; font-size: 1.875rem;" @mousedown="ptzCamera('zoomout')" @mouseup="ptzCamera('stop')"><i class="el-icon-zoom-out control-zoom-btn"></i></div> @mouseup="ptzCamera('stop')"><i class="el-icon-zoom-in control-zoom-btn"
style="font-size: 1.875rem;"></i></div>
<div style="position: absolute; left: 7.25rem; top: 3.25rem; font-size: 1.875rem;"
@mousedown="ptzCamera('zoomout')" @mouseup="ptzCamera('stop')"><i
class="el-icon-zoom-out control-zoom-btn"></i></div>
<div class="contro-speed" style="position: absolute; left: 4px; top: 7rem; width: 9rem;"> <div class="contro-speed" style="position: absolute; left: 4px; top: 7rem; width: 9rem;">
<el-slider v-model="controSpeed" :max="255"></el-slider> <el-slider v-model="controSpeed" :max="255"></el-slider>
</div> </div>
@ -174,32 +188,84 @@
<div class="control-panel"> <div class="control-panel">
<el-button-group> <el-button-group>
<el-tag style="position :absolute; left: 0rem; top: 0rem; width: 5rem; text-align: center" size="medium">预置位编号</el-tag> <el-tag style="position :absolute; left: 0rem; top: 0rem; width: 5rem; text-align: center"
<el-input-number style="position: absolute; left: 5rem; top: 0rem; width: 6rem" size="mini" v-model="presetPos" controls-position="right" :precision="0" :step="1" :min="1" :max="255"></el-input-number> size="medium">预置位编号
<el-button style="position: absolute; left: 11rem; top: 0rem; width: 5rem" size="mini" icon="el-icon-add-location" @click="presetPosition(129, presetPos)">设置</el-button> </el-tag>
<el-button style="position: absolute; left: 27rem; top: 0rem; width: 5rem" size="mini" type="primary" icon="el-icon-place" @click="presetPosition(130, presetPos)">调用</el-button> <el-input-number style="position: absolute; left: 5rem; top: 0rem; width: 6rem" size="mini"
<el-button style="position: absolute; left: 16rem; top: 0rem; width: 5rem" size="mini" icon="el-icon-delete-location" @click="presetPosition(131, presetPos)">删除</el-button> v-model="presetPos" controls-position="right" :precision="0" :step="1" :min="1"
<el-tag style="position :absolute; left: 0rem; top: 2.5rem; width: 5rem; text-align: center" size="medium">巡航速度</el-tag> :max="255"></el-input-number>
<el-input-number style="position: absolute; left: 5rem; top: 2.5rem; width: 6rem" size="mini" v-model="cruisingSpeed" controls-position="right" :precision="0" :min="1" :max="4095"></el-input-number> <el-button style="position: absolute; left: 11rem; top: 0rem; width: 5rem" size="mini"
<el-button style="position: absolute; left: 11rem; top: 2.5rem; width: 5rem" size="mini" icon="el-icon-loading" @click="setSpeedOrTime(134, cruisingGroup, cruisingSpeed)">设置</el-button> icon="el-icon-add-location" @click="presetPosition(129, presetPos)">设置
<el-tag style="position :absolute; left: 16rem; top: 2.5rem; width: 5rem; text-align: center" size="medium">停留时间</el-tag> </el-button>
<el-input-number style="position: absolute; left: 21rem; top: 2.5rem; width: 6rem" size="mini" v-model="cruisingTime" controls-position="right" :precision="0" :min="1" :max="4095"></el-input-number> <el-button style="position: absolute; left: 27rem; top: 0rem; width: 5rem" size="mini" type="primary"
<el-button style="position: absolute; left: 27rem; top: 2.5rem; width: 5rem" size="mini" icon="el-icon-timer" @click="setSpeedOrTime(135, cruisingGroup, cruisingTime)">设置</el-button> icon="el-icon-place" @click="presetPosition(130, presetPos)">调用
<el-tag style="position :absolute; left: 0rem; top: 4.5rem; width: 5rem; text-align: center" size="medium">巡航组编号</el-tag> </el-button>
<el-input-number style="position: absolute; left: 5rem; top: 4.5rem; width: 6rem" size="mini" v-model="cruisingGroup" controls-position="right" :precision="0" :min="0" :max="255"></el-input-number> <el-button style="position: absolute; left: 16rem; top: 0rem; width: 5rem" size="mini"
<el-button style="position: absolute; left: 11rem; top: 4.5rem; width: 5rem" size="mini" icon="el-icon-add-location" @click="setCommand(132, cruisingGroup, presetPos)">添加点</el-button> icon="el-icon-delete-location" @click="presetPosition(131, presetPos)">删除
<el-button style="position: absolute; left: 16rem; top: 4.5rem; width: 5rem" size="mini" icon="el-icon-delete-location" @click="setCommand(133, cruisingGroup, presetPos)">删除点</el-button> </el-button>
<el-button style="position: absolute; left: 21rem; top: 4.5rem; width: 5rem" size="mini" icon="el-icon-delete" @click="setCommand(133, cruisingGroup, 0)">删除组</el-button> <el-tag style="position :absolute; left: 0rem; top: 2.5rem; width: 5rem; text-align: center"
<el-button style="position: absolute; left: 27rem; top: 5rem; width: 5rem" size="mini" type="primary" icon="el-icon-video-camera-solid" @click="setCommand(136, cruisingGroup, 0)">巡航</el-button> size="medium">巡航速度
<el-tag style="position :absolute; left: 0rem; top: 7rem; width: 5rem; text-align: center" size="medium">扫描速度</el-tag> </el-tag>
<el-input-number style="position: absolute; left: 5rem; top: 7rem; width: 6rem" size="mini" v-model="scanSpeed" controls-position="right" :precision="0" :min="1" :max="4095"></el-input-number> <el-input-number style="position: absolute; left: 5rem; top: 2.5rem; width: 6rem" size="mini"
<el-button style="position: absolute; left: 11rem; top: 7rem; width: 5rem" size="mini" icon="el-icon-loading" @click="setSpeedOrTime(138, scanGroup, scanSpeed)">设置</el-button> v-model="cruisingSpeed" controls-position="right" :precision="0" :min="1"
<el-tag style="position :absolute; left: 0rem; top: 9rem; width: 5rem; text-align: center" size="medium">扫描组编号</el-tag> :max="4095"></el-input-number>
<el-input-number style="position: absolute; left: 5rem; top: 9rem; width: 6rem" size="mini" v-model="scanGroup" controls-position="right" :precision="0" :step="1" :min="0" :max="255"></el-input-number> <el-button style="position: absolute; left: 11rem; top: 2.5rem; width: 5rem" size="mini"
<el-button style="position: absolute; left: 11rem; top: 9rem; width: 5rem" size="mini" icon="el-icon-d-arrow-left" @click="setCommand(137, scanGroup, 1)">左边界</el-button> icon="el-icon-loading" @click="setSpeedOrTime(134, cruisingGroup, cruisingSpeed)">设置
<el-button style="position: absolute; left: 16rem; top: 9rem; width: 5rem" size="mini" icon="el-icon-d-arrow-right" @click="setCommand(137, scanGroup, 2)">右边界</el-button> </el-button>
<el-button style="position: absolute; left: 27rem; top: 7rem; width: 5rem" size="mini" type="primary" icon="el-icon-video-camera-solid" @click="setCommand(137, scanGroup, 0)">扫描</el-button> <el-tag style="position :absolute; left: 16rem; top: 2.5rem; width: 5rem; text-align: center"
<el-button style="position: absolute; left: 27rem; top: 9rem; width: 5rem" size="mini" type="danger" icon="el-icon-switch-button" @click="ptzCamera('stop')">停止</el-button> size="medium">停留时间
</el-tag>
<el-input-number style="position: absolute; left: 21rem; top: 2.5rem; width: 6rem" size="mini"
v-model="cruisingTime" controls-position="right" :precision="0" :min="1"
:max="4095"></el-input-number>
<el-button style="position: absolute; left: 27rem; top: 2.5rem; width: 5rem" size="mini"
icon="el-icon-timer" @click="setSpeedOrTime(135, cruisingGroup, cruisingTime)">设置
</el-button>
<el-tag style="position :absolute; left: 0rem; top: 4.5rem; width: 5rem; text-align: center"
size="medium">巡航组编号
</el-tag>
<el-input-number style="position: absolute; left: 5rem; top: 4.5rem; width: 6rem" size="mini"
v-model="cruisingGroup" controls-position="right" :precision="0" :min="0"
:max="255"></el-input-number>
<el-button style="position: absolute; left: 11rem; top: 4.5rem; width: 5rem" size="mini"
icon="el-icon-add-location" @click="setCommand(132, cruisingGroup, presetPos)">添加点
</el-button>
<el-button style="position: absolute; left: 16rem; top: 4.5rem; width: 5rem" size="mini"
icon="el-icon-delete-location" @click="setCommand(133, cruisingGroup, presetPos)">删除点
</el-button>
<el-button style="position: absolute; left: 21rem; top: 4.5rem; width: 5rem" size="mini"
icon="el-icon-delete" @click="setCommand(133, cruisingGroup, 0)">删除组
</el-button>
<el-button style="position: absolute; left: 27rem; top: 5rem; width: 5rem" size="mini" type="primary"
icon="el-icon-video-camera-solid" @click="setCommand(136, cruisingGroup, 0)">巡航
</el-button>
<el-tag style="position :absolute; left: 0rem; top: 7rem; width: 5rem; text-align: center"
size="medium">扫描速度
</el-tag>
<el-input-number style="position: absolute; left: 5rem; top: 7rem; width: 6rem" size="mini"
v-model="scanSpeed" controls-position="right" :precision="0" :min="1"
:max="4095"></el-input-number>
<el-button style="position: absolute; left: 11rem; top: 7rem; width: 5rem" size="mini"
icon="el-icon-loading" @click="setSpeedOrTime(138, scanGroup, scanSpeed)">设置
</el-button>
<el-tag style="position :absolute; left: 0rem; top: 9rem; width: 5rem; text-align: center"
size="medium">扫描组编号
</el-tag>
<el-input-number style="position: absolute; left: 5rem; top: 9rem; width: 6rem" size="mini"
v-model="scanGroup" controls-position="right" :precision="0" :step="1" :min="0"
:max="255"></el-input-number>
<el-button style="position: absolute; left: 11rem; top: 9rem; width: 5rem" size="mini"
icon="el-icon-d-arrow-left" @click="setCommand(137, scanGroup, 1)">左边界
</el-button>
<el-button style="position: absolute; left: 16rem; top: 9rem; width: 5rem" size="mini"
icon="el-icon-d-arrow-right" @click="setCommand(137, scanGroup, 2)">右边界
</el-button>
<el-button style="position: absolute; left: 27rem; top: 7rem; width: 5rem" size="mini" type="primary"
icon="el-icon-video-camera-solid" @click="setCommand(137, scanGroup, 0)">扫描
</el-button>
<el-button style="position: absolute; left: 27rem; top: 9rem; width: 5rem" size="mini" type="danger"
icon="el-icon-switch-button" @click="ptzCamera('stop')">停止
</el-button>
</el-button-group> </el-button-group>
</div> </div>
</div> </div>
@ -231,6 +297,24 @@
</div> </div>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="语音对讲" name="broadcast">
<div style="padding: 0 10px">
<el-switch v-model="broadcastMode" :disabled="broadcastStatus !== -1" active-color="#409EFF"
active-text="喊话(Broadcast)"
inactive-text="对讲(Talk)"></el-switch>
</div>
<div class="trank" style="text-align: center;">
<el-button @click="broadcastStatusClick()" :type="getBroadcastStatus()" :disabled="broadcastStatus === -2"
circle icon="el-icon-microphone" style="font-size: 32px; padding: 24px;margin-top: 24px;"/>
<p>
<span v-if="broadcastStatus === -2">正在释放资源</span>
<span v-if="broadcastStatus === -1">点击开始对讲</span>
<span v-if="broadcastStatus === 0">等待接通中...</span>
<span v-if="broadcastStatus === 1">请说话</span>
</p>
</div>
</el-tab-pane>
</el-tabs> </el-tabs>
</div> </div>
@ -241,7 +325,9 @@
<script> <script>
import rtcPlayer from '../dialog/rtcPlayer.vue' import rtcPlayer from '../dialog/rtcPlayer.vue'
import LivePlayer from '@liveqing/liveplayer' import LivePlayer from '@liveqing/liveplayer'
import crypto from 'crypto'
import jessibucaPlayer from '../common/jessibuca.vue' import jessibucaPlayer from '../common/jessibuca.vue'
export default { export default {
name: 'devicePlayer', name: 'devicePlayer',
props: {}, props: {},
@ -258,7 +344,9 @@ export default {
} }
}, },
created() { created() {
console.log("created")
console.log(this.player) console.log(this.player)
this.broadcastStatus = -1;
if (Object.keys(this.player).length === 1) { if (Object.keys(this.player).length === 1) {
this.activePlayer = Object.keys(this.player)[0] this.activePlayer = Object.keys(this.player)[0]
} }
@ -271,7 +359,6 @@ export default {
// //
player: { player: {
jessibuca: ["ws_flv", "wss_flv"], jessibuca: ["ws_flv", "wss_flv"],
livePlayer : ["ws_flv", "wss_flv"],
webRTC: ["rtc", "rtcs"], webRTC: ["rtc", "rtcs"],
}, },
showVideoDialog: false, showVideoDialog: false,
@ -307,6 +394,9 @@ export default {
recordStartTime: 0, recordStartTime: 0,
showTimeText: "00:00:00", showTimeText: "00:00:00",
streamInfo: null, streamInfo: null,
broadcastMode: true,
broadcastRtc: null,
broadcastStatus: -1, // -2 -1 0 1
}; };
}, },
methods: { methods: {
@ -332,7 +422,8 @@ export default {
type: 'warning' type: 'warning'
}); });
} }
}).catch(function (e) {}); }).catch(function (e) {
});
} }
}, },
changePlayer: function (tab) { changePlayer: function (tab) {
@ -441,7 +532,8 @@ export default {
console.error(res.data.msg) console.error(res.data.msg)
} }
if (callback) callback(); if (callback) callback();
}).catch(function (e) {}); }).catch(function (e) {
});
that.coverPlaying = false; that.coverPlaying = false;
that.convertKey = ""; that.convertKey = "";
// if (callback )callback(); // if (callback )callback();
@ -457,8 +549,6 @@ export default {
this.$refs[this.activePlayer].play(this.getUrlByStreamInfo(streamInfo)) this.$refs[this.activePlayer].play(this.getUrlByStreamInfo(streamInfo))
}); });
} }
}, },
close: function () { close: function () {
console.log('关闭视频'); console.log('关闭视频');
@ -472,6 +562,7 @@ export default {
this.convertStop(); this.convertStop();
} }
this.convertKey = '' this.convertKey = ''
this.stopBroadcast()
}, },
copySharedInfo: function (data) { copySharedInfo: function (data) {
@ -502,7 +593,8 @@ export default {
this.$axios({ this.$axios({
method: 'post', method: 'post',
url: '/api/ptz/control/' + this.deviceId + '/' + this.channelId + '?command=' + command + '&horizonSpeed=' + this.controSpeed + '&verticalSpeed=' + this.controSpeed + '&zoomSpeed=' + this.controSpeed url: '/api/ptz/control/' + this.deviceId + '/' + this.channelId + '?command=' + command + '&horizonSpeed=' + this.controSpeed + '&verticalSpeed=' + this.controSpeed + '&zoomSpeed=' + this.controSpeed
}).then(function (res) {}); }).then(function (res) {
});
}, },
//////////////////////////////////////////////// ////////////////////////////////////////////////
videoError: function (e) { videoError: function (e) {
@ -514,7 +606,8 @@ export default {
this.$axios({ this.$axios({
method: 'post', method: 'post',
url: '/api/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '&parameter1=0&parameter2=' + presetPos + '&combindCode2=0' url: '/api/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '&parameter1=0&parameter2=' + presetPos + '&combindCode2=0'
}).then(function (res) {}); }).then(function (res) {
});
}, },
setSpeedOrTime: function (cmdCode, groupNum, parameter) { setSpeedOrTime: function (cmdCode, groupNum, parameter) {
let that = this; let that = this;
@ -524,7 +617,8 @@ export default {
this.$axios({ this.$axios({
method: 'post', method: 'post',
url: '/api/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '&parameter1=' + groupNum + '&parameter2=' + parameter2 + '&combindCode2=' + combindCode2 url: '/api/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '&parameter1=' + groupNum + '&parameter2=' + parameter2 + '&combindCode2=' + combindCode2
}).then(function (res) {}); }).then(function (res) {
});
}, },
setCommand: function (cmdCode, groupNum, parameter) { setCommand: function (cmdCode, groupNum, parameter) {
let that = this; let that = this;
@ -532,7 +626,8 @@ export default {
this.$axios({ this.$axios({
method: 'post', method: 'post',
url: '/api/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '&parameter1=' + groupNum + '&parameter2=' + parameter + '&combindCode2=0' url: '/api/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '&parameter1=' + groupNum + '&parameter2=' + parameter + '&combindCode2=0'
}).then(function (res) {}); }).then(function (res) {
});
}, },
copyUrl: function (dropdownItem) { copyUrl: function (dropdownItem) {
console.log(dropdownItem) console.log(dropdownItem)
@ -542,9 +637,168 @@ export default {
}) })
}, },
getBroadcastStatus() {
if (this.broadcastStatus == -2) {
return "primary"
}
if (this.broadcastStatus == -1) {
return "primary"
}
if (this.broadcastStatus == 0) {
return "warning"
}
if (this.broadcastStatus == 1) {
return "danger"
}
},
broadcastStatusClick() {
if (this.broadcastStatus == -1) {
//
this.broadcastStatus = 0
//
this.$axios({
method: 'get',
url: '/api/play/broadcast/' + this.deviceId + '/' + this.channelId + "?timeout=30&broadcastMode=" + this.broadcastMode
}).then((res) => {
if (res.data.code === 0) {
let streamInfo = res.data.data.streamInfo;
if (document.location.protocol.includes("https")) {
this.startBroadcast(streamInfo.rtcs)
} else {
this.startBroadcast(streamInfo.rtc)
}
} else {
this.$message({
showClose: true,
message: res.data.msg,
type: "error",
});
}
});
} else if (this.broadcastStatus === 1) {
this.broadcastStatus = -1;
this.broadcastRtc.close()
}
},
startBroadcast(url) {
// Key
this.$axios({
method: 'post',
url: '/api/user/userInfo',
}).then((res) => {
if (res.data.code !== 0) {
this.$message({
showClose: true,
message: "获取推流鉴权Key失败",
type: "error",
});
this.broadcastStatus = -1;
} else {
let pushKey = res.data.data.pushKey;
// KEY
url += "&sign=" + crypto.createHash('md5').update(pushKey, "utf8").digest('hex')
console.log("开始语音喊话: " + url)
this.broadcastRtc = new ZLMRTCClient.Endpoint({
debug: true, //
zlmsdpUrl: url, //
simulecast: false,
useCamera: false,
audioEnable: true,
videoEnable: false,
recvOnly: false,
})
// webrtcPlayer.on(ZLMRTCClient.Events.WEBRTC_ON_REMOTE_STREAMS,(e)=>{//
// console.error('',e.streams)
// this.broadcastStatus = 1;
// });
//
// webrtcPlayer.on(ZLMRTCClient.Events.WEBRTC_ON_LOCAL_STREAM,(s)=>{//
// this.broadcastStatus = 1;
// // document.getElementById('selfVideo').srcObject=s;
// // this.eventcallbacK("LOCAL STREAM", "")
// });
this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_NOT_SUPPORT, (e) => {//
console.error('不支持webrtc', e)
this.$message({
showClose: true,
message: '不支持webrtc, 无法进行语音喊话',
type: 'error'
});
this.broadcastStatus = -1;
});
this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_ICE_CANDIDATE_ERROR, (e) => {// ICE
console.error('ICE 协商出错')
this.$message({
showClose: true,
message: 'ICE 协商出错',
type: 'error'
});
this.broadcastStatus = -1;
});
this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED, (e) => {// offer anwser
console.error('offer anwser 交换失败', e)
this.$message({
showClose: true,
message: 'offer anwser 交换失败' + e,
type: 'error'
});
this.broadcastStatus = -1;
});
this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_ON_CONNECTION_STATE_CHANGE, (e) => {// offer anwser
console.log('状态改变', e)
if (e === "connecting") {
this.broadcastStatus = 0;
} else if (e === "connected") {
this.broadcastStatus = 1;
} else if (e === "disconnected") {
this.broadcastStatus = -1;
}
});
this.broadcastRtc.on(ZLMRTCClient.Events.CAPTURE_STREAM_FAILED, (e) => {// offer anwser
console.log('捕获流失败', e)
this.$message({
showClose: true,
message: '捕获流失败' + e,
type: 'error'
});
this.broadcastStatus = -1;
});
}
}).catch((e) => {
this.$message({
showClose: true,
message: e,
type: 'error'
});
this.broadcastStatus = -1;
});
},
stopBroadcast() {
this.broadcastRtc.close();
this.broadcastStatus = -1;
this.$axios({
method: 'get',
url: '/api/play/broadcast/stop/' + this.deviceId + '/' + this.channelId
}).then((res) => {
if (res.data.code == 0) {
// this.broadcastStatus = -1;
// this.broadcastRtc.close()
} else {
this.$message({
showClose: true,
message: res.data.msg,
type: "error",
});
}
});
}
} }
}; };
</script> </script>
@ -581,6 +835,7 @@ export default {
box-sizing: border-box; box-sizing: border-box;
transition: all 0.3s linear; transition: all 0.3s linear;
} }
.control-btn:hover { .control-btn:hover {
cursor: pointer cursor: pointer
} }
@ -592,9 +847,11 @@ export default {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.control-btn i:hover { .control-btn i:hover {
cursor: pointer cursor: pointer
} }
.control-zoom-btn:hover { .control-zoom-btn:hover {
cursor: pointer cursor: pointer
} }
@ -724,6 +981,7 @@ export default {
.control-bottom .fa { .control-bottom .fa {
transform: rotate(-45deg) translateY(7px); transform: rotate(-45deg) translateY(7px);
} }
.trank { .trank {
width: 80%; width: 80%;
height: 180px; height: 180px;
@ -731,6 +989,7 @@ export default {
padding: 0 10%; padding: 0 10%;
overflow: auto; overflow: auto;
} }
.trankInfo { .trankInfo {
width: 80%; width: 80%;
padding: 0 10%; padding: 0 10%;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -92,6 +92,7 @@ create table wvp_device_channel (
latitude_wgs84 double precision, latitude_wgs84 double precision,
business_group_id character varying(50), business_group_id character varying(50),
gps_time character varying(50), gps_time character varying(50),
stream_identification character varying(50),
constraint uk_wvp_device_channel_unique_device_channel unique (device_id, channel_id) constraint uk_wvp_device_channel_unique_device_channel unique (device_id, channel_id)
); );

View File

@ -93,6 +93,7 @@ create table wvp_device_channel (
business_group_id character varying(50), business_group_id character varying(50),
gps_time character varying(50), gps_time character varying(50),
common_gb_channel_id integer, common_gb_channel_id integer,
stream_identification character varying(50),
constraint uk_wvp_device_channel_unique_device_channel unique (device_id, channel_id) constraint uk_wvp_device_channel_unique_device_channel unique (device_id, channel_id)
); );