级联语音对讲部分
parent
38a85d432a
commit
4b827f3897
|
@ -52,7 +52,7 @@
|
|||
- [X] 报警订阅
|
||||
- [X] 目录订阅
|
||||
- [ ] 语音广播
|
||||
- [ ] 语音对讲
|
||||
- [ ] 语音喊话
|
||||
|
||||
**作为下级平台**
|
||||
- [X] 注册
|
||||
|
@ -91,7 +91,7 @@
|
|||
- [ ] 报警订阅
|
||||
- [X] 目录订阅
|
||||
- [ ] 语音广播
|
||||
- [ ] 语音对讲
|
||||
- [ ] 语音喊话
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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,会话结束成功。
|
|
@ -19,6 +19,7 @@
|
|||
* [树形结构](_content/theory/channel_tree.md)
|
||||
* [注册流程](_content/theory/register.md)
|
||||
* [点播流程](_content/theory/play.md)
|
||||
* [级联语音喊话流程](_content/theory/broadcast_cascade.md)
|
||||
* **必备技巧**
|
||||
* [抓包](_content/skill/tcpdump.md)
|
||||
|
||||
|
|
|
@ -47,6 +47,8 @@ public class UserSetting {
|
|||
|
||||
private String thirdPartyGBIdReg = "[\\s\\S]*";
|
||||
|
||||
private String broadcastForPlatform = "UDP";
|
||||
|
||||
private List<String> interfaceAuthenticationExcludes = new ArrayList<>();
|
||||
|
||||
public Boolean getSavePositionHistory() {
|
||||
|
@ -196,4 +198,12 @@ public class UserSetting {
|
|||
public void setSyncChannelOnDeviceOnline(Boolean syncChannelOnDeviceOnline) {
|
||||
this.syncChannelOnDeviceOnline = syncChannelOnDeviceOnline;
|
||||
}
|
||||
|
||||
public String getBroadcastForPlatform() {
|
||||
return broadcastForPlatform;
|
||||
}
|
||||
|
||||
public void setBroadcastForPlatform(String broadcastForPlatform) {
|
||||
this.broadcastForPlatform = broadcastForPlatform;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
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;
|
||||
|
||||
/**
|
||||
|
@ -10,10 +12,24 @@ import gov.nist.javax.sip.message.SIPResponse;
|
|||
public class AudioBroadcastCatch {
|
||||
|
||||
|
||||
public AudioBroadcastCatch(String deviceId, String channelId, AudioBroadcastCatchStatus status) {
|
||||
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() {
|
||||
|
@ -29,6 +45,26 @@ public class AudioBroadcastCatch {
|
|||
*/
|
||||
private String channelId;
|
||||
|
||||
/**
|
||||
* 流媒体信息
|
||||
*/
|
||||
private MediaServerItem mediaServerItem;
|
||||
|
||||
/**
|
||||
* 关联的流APP
|
||||
*/
|
||||
private String app;
|
||||
|
||||
/**
|
||||
* 关联的流STREAM
|
||||
*/
|
||||
private String stream;
|
||||
|
||||
/**
|
||||
* 是否是级联语音喊话
|
||||
*/
|
||||
private boolean isFromPlatform;
|
||||
|
||||
/**
|
||||
* 语音广播状态
|
||||
*/
|
||||
|
@ -39,6 +75,11 @@ public class AudioBroadcastCatch {
|
|||
*/
|
||||
private SipTransactionInfo sipTransactionInfo;
|
||||
|
||||
/**
|
||||
* 请求结果回调
|
||||
*/
|
||||
private AudioBroadcastEvent event;
|
||||
|
||||
|
||||
public String getDeviceId() {
|
||||
return deviceId;
|
||||
|
@ -75,4 +116,44 @@ public class AudioBroadcastCatch {
|
|||
public void setSipTransactionInfoByRequset(SIPResponse response) {
|
||||
this.sipTransactionInfo = new SipTransactionInfo(response, false);
|
||||
}
|
||||
|
||||
public AudioBroadcastEvent getEvent() {
|
||||
return event;
|
||||
}
|
||||
|
||||
public void setEvent(AudioBroadcastEvent event) {
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
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 MediaServerItem getMediaServerItem() {
|
||||
return mediaServerItem;
|
||||
}
|
||||
|
||||
public void setMediaServerItem(MediaServerItem mediaServerItem) {
|
||||
this.mediaServerItem = mediaServerItem;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package com.genersoft.iot.vmp.gb28181.bean;
|
|||
|
||||
public enum InviteStreamType {
|
||||
|
||||
PLAY,PLAYBACK,PUSH,PROXY,CLOUD_RECORD_PUSH,CLOUD_RECORD_PROXY,TALK
|
||||
PLAY,PLAYBACK,PUSH,PROXY,CLOUD_RECORD_PUSH,CLOUD_RECORD_PROXY,BROADCAST,TALK
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ public class ParentPlatform {
|
|||
* 设备端口
|
||||
*/
|
||||
@Schema(description = "设备端口")
|
||||
private String devicePort;
|
||||
private int devicePort;
|
||||
|
||||
/**
|
||||
* SIP认证用户名(默认使用设备国标编号)
|
||||
|
@ -261,11 +261,11 @@ public class ParentPlatform {
|
|||
this.deviceIp = deviceIp;
|
||||
}
|
||||
|
||||
public String getDevicePort() {
|
||||
public int getDevicePort() {
|
||||
return devicePort;
|
||||
}
|
||||
|
||||
public void setDevicePort(String devicePort) {
|
||||
public void setDevicePort(int devicePort) {
|
||||
this.devicePort = devicePort;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package com.genersoft.iot.vmp.gb28181.event;
|
||||
|
||||
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceNotFoundEvent;
|
||||
import gov.nist.javax.sip.message.SIPRequest;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -87,6 +86,11 @@ public class SipSubscribe {
|
|||
public String callId;
|
||||
public EventObject event;
|
||||
|
||||
public EventResult(int statusCode, String msg) {
|
||||
this.statusCode = statusCode;
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public EventResult(EventObject event) {
|
||||
this.event = event;
|
||||
if (event instanceof ResponseEvent) {
|
||||
|
|
|
@ -23,10 +23,6 @@ public class AudioBroadcastManager {
|
|||
|
||||
public static Map<String, AudioBroadcastCatch> data = new ConcurrentHashMap<>();
|
||||
|
||||
public void add(AudioBroadcastCatch audioBroadcastCatch) {
|
||||
this.update(audioBroadcastCatch);
|
||||
}
|
||||
|
||||
public void update(AudioBroadcastCatch audioBroadcastCatch) {
|
||||
if (SipUtils.isFrontEnd(audioBroadcastCatch.getDeviceId())) {
|
||||
data.put(audioBroadcastCatch.getDeviceId(), audioBroadcastCatch);
|
||||
|
|
|
@ -49,8 +49,6 @@ public class DeferredResultHolder {
|
|||
|
||||
public static final String CALLBACK_CMD_ALARM = "CALLBACK_ALARM";
|
||||
|
||||
public static final String CALLBACK_CMD_BROADCAST = "CALLBACK_BROADCAST";
|
||||
|
||||
private Map<String, Map<String, DeferredResultEx>> map = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
package com.genersoft.iot.vmp.gb28181.transmit.cmd;
|
||||
|
||||
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
||||
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
|
||||
import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
|
||||
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
|
||||
|
||||
import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.SipException;
|
||||
|
@ -14,77 +18,98 @@ public interface ISIPCommanderForPlatform {
|
|||
|
||||
/**
|
||||
* 向上级平台注册
|
||||
*
|
||||
* @param parentPlatform
|
||||
* @return
|
||||
*/
|
||||
void register(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException;
|
||||
void register(ParentPlatform parentPlatform, String callId, WWWAuthenticateHeader www, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent, boolean registerAgain, boolean isRegister) throws SipException, InvalidArgumentException, ParseException;
|
||||
void register(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent)
|
||||
throws InvalidArgumentException, ParseException, SipException;
|
||||
|
||||
void register(ParentPlatform parentPlatform, String callId, WWWAuthenticateHeader www,
|
||||
SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent, boolean registerAgain, boolean isRegister)
|
||||
throws SipException, InvalidArgumentException, ParseException;
|
||||
|
||||
/**
|
||||
* 向上级平台注销
|
||||
*
|
||||
* @param parentPlatform
|
||||
* @return
|
||||
*/
|
||||
void unregister(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException;
|
||||
void unregister(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent)
|
||||
throws InvalidArgumentException, ParseException, SipException;
|
||||
|
||||
|
||||
/**
|
||||
* 向上级平发送心跳信息
|
||||
*
|
||||
* @param parentPlatform
|
||||
* @return callId(作为接受回复的判定)
|
||||
*/
|
||||
String keepalive(ParentPlatform parentPlatform,SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException;
|
||||
String keepalive(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent)
|
||||
throws SipException, InvalidArgumentException, ParseException;
|
||||
|
||||
|
||||
/**
|
||||
* 向上级回复通道信息
|
||||
* @param channel 通道信息
|
||||
*
|
||||
* @param channel 通道信息
|
||||
* @param parentPlatform 平台信息
|
||||
* @param sn
|
||||
* @param fromTag
|
||||
* @param size
|
||||
* @return
|
||||
*/
|
||||
void catalogQuery(DeviceChannel channel, ParentPlatform parentPlatform, String sn, String fromTag, int size) throws SipException, InvalidArgumentException, ParseException;
|
||||
void catalogQuery(List<DeviceChannel> channels, ParentPlatform parentPlatform, String sn, String fromTag) throws InvalidArgumentException, ParseException, SipException;
|
||||
void catalogQuery(DeviceChannel channel, ParentPlatform parentPlatform, String sn, String fromTag, int size)
|
||||
throws SipException, InvalidArgumentException, ParseException;
|
||||
|
||||
void catalogQuery(List<DeviceChannel> channels, ParentPlatform parentPlatform, String sn, String fromTag)
|
||||
throws InvalidArgumentException, ParseException, SipException;
|
||||
|
||||
/**
|
||||
* 向上级回复DeviceInfo查询信息
|
||||
*
|
||||
* @param parentPlatform 平台信息
|
||||
* @param sn
|
||||
* @param fromTag
|
||||
* @return
|
||||
*/
|
||||
void deviceInfoResponse(ParentPlatform parentPlatform, String sn, String fromTag) throws SipException, InvalidArgumentException, ParseException;
|
||||
void deviceInfoResponse(ParentPlatform parentPlatform, String sn, String fromTag)
|
||||
throws SipException, InvalidArgumentException, ParseException;
|
||||
|
||||
/**
|
||||
* 向上级回复DeviceStatus查询信息
|
||||
*
|
||||
* @param parentPlatform 平台信息
|
||||
* @param sn
|
||||
* @param fromTag
|
||||
* @return
|
||||
*/
|
||||
void deviceStatusResponse(ParentPlatform parentPlatform, String sn, String fromTag) throws SipException, InvalidArgumentException, ParseException;
|
||||
void deviceStatusResponse(ParentPlatform parentPlatform, String sn, String fromTag)
|
||||
throws SipException, InvalidArgumentException, ParseException;
|
||||
|
||||
/**
|
||||
* 向上级回复移动位置订阅消息
|
||||
*
|
||||
* @param parentPlatform 平台信息
|
||||
* @param gpsMsgInfo GPS信息
|
||||
* @param subscribeInfo 订阅相关的信息
|
||||
* @param gpsMsgInfo GPS信息
|
||||
* @param subscribeInfo 订阅相关的信息
|
||||
* @return
|
||||
*/
|
||||
void sendNotifyMobilePosition(ParentPlatform parentPlatform, GPSMsgInfo gpsMsgInfo, SubscribeInfo subscribeInfo) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException;
|
||||
void sendNotifyMobilePosition(ParentPlatform parentPlatform, GPSMsgInfo gpsMsgInfo, SubscribeInfo subscribeInfo)
|
||||
throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException;
|
||||
|
||||
/**
|
||||
* 向上级回复报警消息
|
||||
*
|
||||
* @param parentPlatform 平台信息
|
||||
* @param deviceAlarm 报警信息信息
|
||||
* @param deviceAlarm 报警信息信息
|
||||
* @return
|
||||
*/
|
||||
void sendAlarmMessage(ParentPlatform parentPlatform, DeviceAlarm deviceAlarm) throws SipException, InvalidArgumentException, ParseException;
|
||||
|
||||
/**
|
||||
* 回复catalog事件-增加/更新
|
||||
*
|
||||
* @param parentPlatform
|
||||
* @param deviceChannels
|
||||
*/
|
||||
|
@ -92,22 +117,28 @@ public interface ISIPCommanderForPlatform {
|
|||
|
||||
/**
|
||||
* 回复catalog事件-删除
|
||||
*
|
||||
* @param parentPlatform
|
||||
* @param deviceChannels
|
||||
*/
|
||||
void sendNotifyForCatalogOther(String type, ParentPlatform parentPlatform, List<DeviceChannel> deviceChannels, SubscribeInfo subscribeInfo, Integer index) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException;
|
||||
void sendNotifyForCatalogOther(String type, ParentPlatform parentPlatform, List<DeviceChannel> deviceChannels,
|
||||
SubscribeInfo subscribeInfo, Integer index) throws InvalidArgumentException,
|
||||
ParseException, NoSuchFieldException, SipException, IllegalAccessException;
|
||||
|
||||
/**
|
||||
* 回复recordInfo
|
||||
* @param deviceChannel 通道信息
|
||||
*
|
||||
* @param deviceChannel 通道信息
|
||||
* @param parentPlatform 平台信息
|
||||
* @param fromTag fromTag
|
||||
* @param recordInfo 录像信息
|
||||
* @param fromTag fromTag
|
||||
* @param recordInfo 录像信息
|
||||
*/
|
||||
void recordInfo(DeviceChannel deviceChannel, ParentPlatform parentPlatform, String fromTag, RecordInfo recordInfo) throws SipException, InvalidArgumentException, ParseException;
|
||||
void recordInfo(DeviceChannel deviceChannel, ParentPlatform parentPlatform, String fromTag, RecordInfo recordInfo)
|
||||
throws SipException, InvalidArgumentException, ParseException;
|
||||
|
||||
/**
|
||||
* 录像播放推送完成时发送MediaStatus消息
|
||||
*
|
||||
* @param platform
|
||||
* @param sendRtpItem
|
||||
* @return
|
||||
|
@ -116,9 +147,19 @@ public interface ISIPCommanderForPlatform {
|
|||
|
||||
/**
|
||||
* 向发起点播的上级回复bye
|
||||
*
|
||||
* @param platform 平台信息
|
||||
* @param callId callId
|
||||
* @param callId callId
|
||||
*/
|
||||
void streamByeCmd(ParentPlatform platform, String callId) throws SipException, InvalidArgumentException, ParseException;
|
||||
|
||||
void streamByeCmd(ParentPlatform platform, SendRtpItem sendRtpItem) throws SipException, InvalidArgumentException, ParseException;
|
||||
|
||||
void streamByeCmd(ParentPlatform platform, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException;
|
||||
|
||||
void broadcastInviteCmd(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem,
|
||||
SSRCInfo ssrcInfo, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent,
|
||||
SipSubscribe.Event errorEvent) throws ParseException, SipException, InvalidArgumentException;
|
||||
|
||||
void broadcastResultCmd(ParentPlatform platform, DeviceChannel deviceChannel, String sn, boolean result, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import com.genersoft.iot.vmp.conf.SipConfig;
|
|||
import com.genersoft.iot.vmp.gb28181.SipLayer;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
|
@ -14,7 +15,8 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.DigestUtils;
|
||||
|
||||
import javax.sip.*;
|
||||
import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.PeerUnavailableException;
|
||||
import javax.sip.address.Address;
|
||||
import javax.sip.address.SipURI;
|
||||
import javax.sip.header.*;
|
||||
|
@ -22,7 +24,6 @@ import javax.sip.message.Request;
|
|||
import javax.validation.constraints.NotNull;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
|
@ -175,7 +176,7 @@ public class SIPRequestHeaderPlarformProvider {
|
|||
SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), serverAddress);
|
||||
// via
|
||||
ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
|
||||
ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), Integer.parseInt(parentPlatform.getDevicePort()),
|
||||
ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), parentPlatform.getDevicePort(),
|
||||
parentPlatform.getTransport(), viaTag);
|
||||
viaHeader.setRPort();
|
||||
viaHeaders.add(viaHeader);
|
||||
|
@ -212,7 +213,7 @@ public class SIPRequestHeaderPlarformProvider {
|
|||
SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP()+ ":" + parentPlatform.getServerPort());
|
||||
// via
|
||||
ArrayList<ViaHeader> viaHeaders = new ArrayList<>();
|
||||
ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), Integer.parseInt(parentPlatform.getDevicePort()),
|
||||
ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), parentPlatform.getDevicePort(),
|
||||
parentPlatform.getTransport(), SipUtils.getNewViaTag());
|
||||
viaHeader.setRPort();
|
||||
viaHeaders.add(viaHeader);
|
||||
|
@ -272,7 +273,7 @@ public class SIPRequestHeaderPlarformProvider {
|
|||
SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(platform.getServerGBId(), platform.getServerIP()+ ":" + platform.getServerPort());
|
||||
// via
|
||||
ArrayList<ViaHeader> viaHeaders = new ArrayList<>();
|
||||
ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(platform.getDeviceIp(), Integer.parseInt(platform.getDevicePort()),
|
||||
ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(platform.getDeviceIp(), platform.getDevicePort(),
|
||||
platform.getTransport(), SipUtils.getNewViaTag());
|
||||
viaHeader.setRPort();
|
||||
viaHeaders.add(viaHeader);
|
||||
|
@ -306,6 +307,83 @@ public class SIPRequestHeaderPlarformProvider {
|
|||
.createSipURI(platform.getDeviceGBId(), sipAddress));
|
||||
request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress));
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
public Request createInviteRequest(ParentPlatform platform, String channelId, String toString, String viaTag, String fromTag, Object content, String ssrc, CallIdHeader callIdHeader, String transport) throws PeerUnavailableException, ParseException, InvalidArgumentException {
|
||||
Request request = null;
|
||||
//请求行
|
||||
String deviceHostAddress = platform.getDeviceIp() + ":" + platform.getDevicePort();
|
||||
SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, deviceHostAddress);
|
||||
//via
|
||||
ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
|
||||
HeaderFactory headerFactory = sipLayer.getSipFactory().createHeaderFactory();
|
||||
ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(platform.getDeviceIp()), platform.getDevicePort(), platform.getTransport(), viaTag);
|
||||
viaHeader.setRPort();
|
||||
viaHeaders.add(viaHeader);
|
||||
|
||||
//from
|
||||
SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
|
||||
Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
|
||||
FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack
|
||||
//to
|
||||
SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, deviceHostAddress);
|
||||
Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
|
||||
ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress,null);
|
||||
|
||||
//Forwards
|
||||
MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
|
||||
|
||||
//ceq
|
||||
CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE);
|
||||
request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
|
||||
|
||||
request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
|
||||
|
||||
Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(platform.getDeviceIp())+":"+ deviceHostAddress));
|
||||
// Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), device.getHost().getIp()+":"+device.getHost().getPort()));
|
||||
request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress));
|
||||
// Subject
|
||||
SubjectHeader subjectHeader = sipLayer.getSipFactory().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0));
|
||||
request.addHeader(subjectHeader);
|
||||
ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().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 = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, deviceHostAddress);
|
||||
|
||||
// via
|
||||
ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
|
||||
ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(platform.getDeviceIp()), platform.getDevicePort(), platform.getTransport(), SipUtils.getNewViaTag());
|
||||
viaHeaders.add(viaHeader);
|
||||
//from
|
||||
SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain());
|
||||
Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
|
||||
FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.isFromServer()?transactionInfo.getFromTag():transactionInfo.getToTag());
|
||||
//to
|
||||
SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, deviceHostAddress);
|
||||
Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
|
||||
ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress,transactionInfo.isFromServer()?transactionInfo.getToTag():transactionInfo.getFromTag());
|
||||
|
||||
//Forwards
|
||||
MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
|
||||
|
||||
//ceq
|
||||
CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE);
|
||||
CallIdHeader callIdHeader = sipLayer.getSipFactory().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId());
|
||||
request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestLine, Request.BYE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
|
||||
|
||||
request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
|
||||
|
||||
Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(platform.getDeviceIp())+":"+ platform.getDevicePort()));
|
||||
request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress));
|
||||
|
||||
request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
|
||||
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -590,12 +590,12 @@ public class SIPCommander implements ISIPCommander {
|
|||
return;
|
||||
}
|
||||
if (!mediaServerItem.isRtpEnable()) {
|
||||
// 单端口暂不支持语音对讲
|
||||
logger.info("[语音对讲] 单端口暂不支持此操作");
|
||||
// 单端口暂不支持语音喊话
|
||||
logger.info("[语音喊话] 单端口暂不支持此操作");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("[语音对讲] {} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
|
||||
logger.info("[语音喊话] {} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
|
||||
HookSubscribeForStreamChange hookSubscribeForStreamChange = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId());
|
||||
subscribe.addSubscribe(hookSubscribeForStreamChange, (MediaServerItem mediaServerItemInUse, JSONObject json) -> {
|
||||
if (event != null) {
|
||||
|
|
|
@ -1,22 +1,31 @@
|
|||
package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
||||
import com.genersoft.iot.vmp.gb28181.SipLayer;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
||||
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
|
||||
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderPlarformProvider;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
|
||||
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.dto.MediaServerItem;
|
||||
import com.genersoft.iot.vmp.service.IMediaServerService;
|
||||
import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
|
||||
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo;
|
||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
||||
import gov.nist.javax.sip.message.MessageFactoryImpl;
|
||||
import gov.nist.javax.sip.message.SIPRequest;
|
||||
import gov.nist.javax.sip.message.SIPResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
@ -26,6 +35,7 @@ import org.springframework.stereotype.Component;
|
|||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.ResponseEvent;
|
||||
import javax.sip.SipException;
|
||||
import javax.sip.header.CallIdHeader;
|
||||
import javax.sip.header.WWWAuthenticateHeader;
|
||||
|
@ -61,6 +71,16 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
|
|||
@Autowired
|
||||
private SIPSender sipSender;
|
||||
|
||||
@Autowired
|
||||
private ZlmHttpHookSubscribe subscribe;
|
||||
|
||||
@Autowired
|
||||
private UserSetting userSetting;
|
||||
|
||||
|
||||
@Autowired
|
||||
private VideoStreamSessionManager streamSession;
|
||||
|
||||
@Override
|
||||
public void register(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException {
|
||||
register(parentPlatform, null, null, errorEvent, okEvent, false, true);
|
||||
|
@ -645,4 +665,107 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
|
|||
}
|
||||
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("<Notify>\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("</Notify>\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, JSONObject json) -> {
|
||||
if (event != null) {
|
||||
event.response(mediaServerItemInUse, json);
|
||||
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=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 8 96\r\n");
|
||||
} else if ("TCP-ACTIVE".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) {
|
||||
content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 8 96\r\n");
|
||||
} else if ("UDP".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) {
|
||||
content.append("m=video " + 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(), null, ssrcInfo.getSsrc(),
|
||||
callIdHeader ,platform.getTransport());
|
||||
sipSender.transmitRequest(sipLayer.getLocalIp(platform.getDeviceIp()), request, (e -> {
|
||||
streamSession.remove(platform.getServerGBId(), channelId, ssrcInfo.getStream());
|
||||
mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
|
||||
errorEvent.response(e);
|
||||
}), e -> {
|
||||
// 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值
|
||||
ResponseEvent responseEvent = (ResponseEvent) e.event;
|
||||
SIPResponse response = (SIPResponse) responseEvent.getResponse();
|
||||
streamSession.put(platform.getServerGBId(), channelId, callIdHeader.getCallId(), stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.play);
|
||||
okEvent.response(e);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,8 +16,6 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor
|
|||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
|
||||
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.HookSubscribeForRtpServerTimeout;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
|
||||
import com.genersoft.iot.vmp.service.IDeviceService;
|
||||
import com.genersoft.iot.vmp.service.IMediaServerService;
|
||||
|
@ -40,8 +38,6 @@ import javax.sip.header.FromHeader;
|
|||
import javax.sip.header.HeaderAddress;
|
||||
import javax.sip.header.ToHeader;
|
||||
import java.text.ParseException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* SIP命令类型: ACK请求
|
||||
|
@ -123,68 +119,31 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
|
|||
MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
|
||||
logger.info("收到ACK,rtp/{}开始向上级推流, 目标={}:{},SSRC={}, RTCP={}", sendRtpItem.getStreamId(),
|
||||
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.getStreamId());
|
||||
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) {
|
||||
RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance(
|
||||
sendRtpItem.getMediaServerId(), sendRtpItem.getApp(), sendRtpItem.getStreamId(),
|
||||
sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isTcp(),
|
||||
sendRtpItem.getLocalPort(), sendRtpItem.getPt(), sendRtpItem.isUsePs(), sendRtpItem.isOnlyAudio());
|
||||
redisGbPlayMsgListener.sendMsgForStartSendRtpStream(sendRtpItem.getServerId(), requestPushStreamMsg, json -> {
|
||||
startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, json, param, callIdHeader);
|
||||
startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, json, callIdHeader);
|
||||
});
|
||||
} else {
|
||||
// 如果是非严格模式,需要关闭端口占用
|
||||
JSONObject startSendRtpStreamResult = null;
|
||||
if (sendRtpItem.getLocalPort() != 0) {
|
||||
HookSubscribeForRtpServerTimeout hookSubscribeForRtpServerTimeout = HookSubscribeFactory.on_rtp_server_timeout(sendRtpItem.getSsrc(), null, mediaInfo.getId());
|
||||
hookSubscribe.removeSubscribe(hookSubscribeForRtpServerTimeout);
|
||||
if (zlmrtpServerFactory.releasePort(mediaInfo, sendRtpItem.getSsrc())) {
|
||||
if (sendRtpItem.isTcpActive()) {
|
||||
startSendRtpStreamResult = zlmrtpServerFactory.startSendRtpPassive(mediaInfo, param);
|
||||
}else {
|
||||
param.put("is_udp", is_Udp);
|
||||
param.put("dst_url", sendRtpItem.getIp());
|
||||
param.put("dst_port", sendRtpItem.getPort());
|
||||
startSendRtpStreamResult = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
|
||||
}
|
||||
}
|
||||
}else {
|
||||
if (sendRtpItem.isTcpActive()) {
|
||||
startSendRtpStreamResult = zlmrtpServerFactory.startSendRtpPassive(mediaInfo, param);
|
||||
}else {
|
||||
param.put("is_udp", is_Udp);
|
||||
param.put("dst_url", sendRtpItem.getIp());
|
||||
param.put("dst_port", sendRtpItem.getPort());
|
||||
startSendRtpStreamResult = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
|
||||
}
|
||||
}
|
||||
}else {
|
||||
JSONObject startSendRtpStreamResult = zlmrtpServerFactory.startSendRtp(mediaInfo, sendRtpItem);
|
||||
if (startSendRtpStreamResult != null) {
|
||||
startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, startSendRtpStreamResult, param, callIdHeader);
|
||||
startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, startSendRtpStreamResult, callIdHeader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startSendRtpStreamHand(RequestEvent evt, SendRtpItem sendRtpItem, ParentPlatform parentPlatform,
|
||||
JSONObject jsonObject, Map<String, Object> param, CallIdHeader callIdHeader) {
|
||||
JSONObject jsonObject, 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"));
|
||||
logger.info("RTP推流成功[ {}/{} ],{}->{}:{}, " ,sendRtpItem.getApp(), sendRtpItem.getStreamId(), sendRtpItem.getIp(), sendRtpItem.getIp(), sendRtpItem.getPort());
|
||||
} else {
|
||||
logger.error("RTP推流失败: {}, 参数:{}",jsonObject.getString("msg"), JSON.toJSONString(param));
|
||||
logger.error("RTP推流失败: {}, 参数:{}",jsonObject.getString("msg"), JSON.toJSONString(sendRtpItem));
|
||||
if (sendRtpItem.isOnlyAudio()) {
|
||||
Device device = deviceService.getDevice(sendRtpItem.getDeviceId());
|
||||
AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
|
||||
|
@ -193,7 +152,7 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
|
|||
cmder.streamByeCmd(device, sendRtpItem.getChannelId(), audioBroadcastCatch.getSipTransactionInfo(), null);
|
||||
} catch (SipException | ParseException | InvalidArgumentException |
|
||||
SsrcTransactionNotFoundException e) {
|
||||
logger.error("[命令发送失败] 停止语音对讲: {}", e.getMessage());
|
||||
logger.error("[命令发送失败] 停止语音喊话: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}else {
|
||||
|
|
|
@ -201,7 +201,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
|||
|
||||
MediaServerItem mediaServerItem = null;
|
||||
StreamPushItem streamPushItem = null;
|
||||
StreamProxyItem proxyByAppAndStream =null;
|
||||
StreamProxyItem proxyByAppAndStream = null;
|
||||
// 不是通道可能是直播流
|
||||
if (channel != null && gbStream == null) {
|
||||
// 通道存在,发100,TRYING
|
||||
|
@ -1001,7 +1001,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
|||
String stream = device.getDeviceId() + "_" + audioBroadcastCatch.getChannelId();
|
||||
|
||||
CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME);
|
||||
sendRtpItem.setPlayType(InviteStreamType.TALK);
|
||||
sendRtpItem.setPlayType(InviteStreamType.BROADCAST);
|
||||
sendRtpItem.setCallId(callIdHeader.getCallId());
|
||||
sendRtpItem.setPlatformId(requesterId);
|
||||
sendRtpItem.setStatus(1);
|
||||
|
@ -1013,6 +1013,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
|||
sendRtpItem.setOnlyAudio(true);
|
||||
redisCatchStorage.updateSendRTPSever(sendRtpItem);
|
||||
|
||||
|
||||
Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, app, stream);
|
||||
if (streamReady) {
|
||||
sendOk(device, sendRtpItem, sdp, request, mediaServerItem, mediaTransmissionTCP, ssrc);
|
||||
|
@ -1084,7 +1085,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
|||
audioBroadcastManager.update(audioBroadcastCatch);
|
||||
|
||||
} catch (SipException | InvalidArgumentException | ParseException | SdpParseException e) {
|
||||
logger.error("[命令发送失败] 语音对讲 回复200OK(SDP): {}", e.getMessage());
|
||||
logger.error("[命令发送失败] 语音喊话 回复200OK(SDP): {}", e.getMessage());
|
||||
}
|
||||
return sipResponse;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,200 @@
|
|||
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.ZLMRTPServerFactory;
|
||||
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.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 ZLMRTPServerFactory zlmrtpServerFactory;
|
||||
|
||||
@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) {
|
||||
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();
|
||||
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, response)->{
|
||||
// 上级平台推流成功
|
||||
String app = response.getString("app");
|
||||
String stream = response.getString("stream");
|
||||
AudioBroadcastCatch broadcastCatch = audioBroadcastManager.get(device.getDeviceId(), targetId);
|
||||
if (broadcastCatch != null ) {
|
||||
if (playService.audioBroadcastInUse(device, targetId)) {
|
||||
logger.info("[国标级联] 语音喊话 设备正正在使用中 platform: {}, channel: {}",
|
||||
platform.getServerGBId(), deviceChannel.getChannelId());
|
||||
// 查看语音通道已经建立且已经占用 回复BYE
|
||||
try {
|
||||
platformService.stopBroadcast(platform, deviceChannel.getChannelId(), stream);
|
||||
} catch (InvalidArgumentException | ParseException | SsrcTransactionNotFoundException |
|
||||
SipException e) {
|
||||
logger.info("[消息发送失败] 国标级联 语音喊话 platform: {}, channel: {}", platform.getServerGBId(), deviceChannel.getChannelId());
|
||||
}
|
||||
}else {
|
||||
// 查看语音通道已经建立但是未占用
|
||||
broadcastCatch.setApp(app);
|
||||
broadcastCatch.setStream(stream);
|
||||
broadcastCatch.setMediaServerItem(mediaServerItem);
|
||||
audioBroadcastManager.update(broadcastCatch);
|
||||
// 推流到设备
|
||||
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, targetId, stream, null);
|
||||
if (sendRtpItem == null) {
|
||||
logger.warn("[国标级联] 语音喊话 异常,未找到发流信息, channelId: {}, stream: {}", targetId, stream);
|
||||
logger.info("[国标级联] 语音喊话 重新开始,channelId: {}, stream: {}", targetId, stream);
|
||||
try {
|
||||
playService.audioBroadcastCmd(device, targetId, mediaServerItem, app, stream, 60, true, msg -> {
|
||||
logger.info("[语音喊话] 通道建立成功, device: {}, channel: {}", device.getDeviceId(), targetId);
|
||||
});
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
logger.info("[消息发送失败] 国标级联 语音喊话 platform: {}", platform.getServerGBId());
|
||||
}
|
||||
}else {
|
||||
// 发流
|
||||
JSONObject jsonObject = zlmrtpServerFactory.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, app, stream, 60, true, msg -> {
|
||||
logger.info("[语音喊话] 通道建立成功, device: {}, channel: {}", device.getDeviceId(), targetId);
|
||||
});
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
logger.info("[消息发送失败] 国标级联 语音喊话 platform: {}", platform.getServerGBId());
|
||||
}
|
||||
}
|
||||
|
||||
}, eventResultForBroadcastInvite -> {
|
||||
// 收到错误
|
||||
logger.info("[国标级联-语音喊话] 与下级通道建立失败 device: {}, channel: {}, 错误:{}/{}", device.getDeviceId(),
|
||||
targetId, eventResultForBroadcastInvite.statusCode, eventResultForBroadcastInvite.msg);
|
||||
}, (code, msg)->{
|
||||
// 超时
|
||||
logger.info("[国标级联-语音喊话] 与下级通道建立超时 device: {}, channel: {}, 错误:{}/{}", device.getDeviceId(),
|
||||
targetId, code, msg);
|
||||
});
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
logger.info("[消息发送失败] 国标级联 语音喊话 invite消息 platform: {}", platform.getServerGBId());
|
||||
}
|
||||
});
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
logger.info("[消息发送失败] 国标级联 语音喊话 platform: {}", platform.getServerGBId());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,17 +1,14 @@
|
|||
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.AudioBroadcastCatch;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.AudioBroadcastCatchStatus;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
|
||||
import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
|
||||
import gov.nist.javax.sip.message.SIPRequest;
|
||||
import org.dom4j.Element;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -52,26 +49,13 @@ public class BroadcastResponseMessageHandler extends SIPRequestProcessorParent i
|
|||
public void handForDevice(RequestEvent evt, Device device, Element rootElement) {
|
||||
try {
|
||||
String channelId = getText(rootElement, "DeviceID");
|
||||
String key = DeferredResultHolder.CALLBACK_CMD_BROADCAST + device.getDeviceId() + channelId;
|
||||
|
||||
// 此处是对本平台发出Broadcast指令的应答
|
||||
JSONObject json = new JSONObject();
|
||||
XmlUtil.node2Json(rootElement, json);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(json.toJSONString());
|
||||
}
|
||||
RequestMessage msg = new RequestMessage();
|
||||
msg.setKey(key);
|
||||
msg.setData(json);
|
||||
deferredResultHolder.invokeAllResult(msg);
|
||||
|
||||
|
||||
if (!audioBroadcastManager.exit(device.getDeviceId(), channelId)) {
|
||||
// 回复410
|
||||
responseAck((SIPRequest) evt.getRequest(), Response.GONE);
|
||||
return;
|
||||
}
|
||||
logger.info("收到语音广播的回复:{}/{}", device.getDeviceId(), channelId );
|
||||
String result = getText(rootElement, "Result");
|
||||
logger.info("收到语音广播的回复 {}:{}/{}", result, device.getDeviceId(), channelId );
|
||||
AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(device.getDeviceId(), channelId);
|
||||
audioBroadcastCatch.setStatus(AudioBroadcastCatchStatus.WaiteInvite);
|
||||
audioBroadcastManager.update(audioBroadcastCatch);
|
||||
|
|
|
@ -1,39 +1,24 @@
|
|||
package com.genersoft.iot.vmp.gb28181.transmit.event.response.impl;
|
||||
|
||||
import com.genersoft.iot.vmp.conf.SipConfig;
|
||||
import com.genersoft.iot.vmp.gb28181.SipLayer;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
|
||||
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.response.SIPResponseProcessorAbstract;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||
import com.genersoft.iot.vmp.service.IDeviceService;
|
||||
import com.genersoft.iot.vmp.utils.GitUtil;
|
||||
import gov.nist.javax.sip.ResponseEventExt;
|
||||
import gov.nist.javax.sip.SipProviderImpl;
|
||||
import gov.nist.javax.sip.message.SIPResponse;
|
||||
import gov.nist.javax.sip.stack.SIPClientTransaction;
|
||||
import gov.nist.javax.sip.stack.SIPDialog;
|
||||
import gov.nist.javax.sip.stack.SIPTransaction;
|
||||
import gov.nist.javax.sip.stack.SIPTransactionImpl;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.sdp.SdpFactory;
|
||||
import javax.sdp.SdpParseException;
|
||||
import javax.sdp.SessionDescription;
|
||||
import javax.sip.*;
|
||||
import javax.sip.address.Address;
|
||||
import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.ResponseEvent;
|
||||
import javax.sip.SipException;
|
||||
import javax.sip.address.SipURI;
|
||||
import javax.sip.header.CSeqHeader;
|
||||
import javax.sip.header.UserAgentHeader;
|
||||
import javax.sip.message.Request;
|
||||
import javax.sip.message.Response;
|
||||
import java.text.ParseException;
|
||||
|
@ -104,6 +89,7 @@ public class InviteResponseProcessor extends SIPResponseProcessorAbstract {
|
|||
} else {
|
||||
sdp = SdpFactory.getInstance().createSessionDescription(contentString);
|
||||
}
|
||||
// 查看是否是来自设备的,此是回复
|
||||
|
||||
SipURI requestUri = sipLayer.getSipFactory().createAddressFactory().createSipURI(sdp.getOrigin().getUsername(), event.getRemoteIpAddress() + ":" + event.getRemotePort());
|
||||
Request reqAck = headerProvider.createAckRequest(response.getLocalAddress().getHostAddress(), requestUri, response);
|
||||
|
|
|
@ -287,15 +287,19 @@ public class ZLMHttpHookListener {
|
|||
logger.info("[ZLM HOOK] 流注销, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
|
||||
}
|
||||
|
||||
JSONObject ret = new JSONObject();
|
||||
ret.put("code", 0);
|
||||
ret.put("msg", "success");
|
||||
|
||||
JSONObject json = (JSONObject) JSON.toJSON(param);
|
||||
MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId());
|
||||
if (mediaInfo == null) {
|
||||
return ret;
|
||||
}
|
||||
taskExecutor.execute(()-> {
|
||||
ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_stream_changed, json);
|
||||
if (subscribe != null) {
|
||||
MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId());
|
||||
if (mediaInfo != null) {
|
||||
subscribe.response(mediaInfo, json);
|
||||
}
|
||||
if (subscribe != null ) {
|
||||
subscribe.response(mediaInfo, json);
|
||||
}
|
||||
// 流消失移除redis play
|
||||
List<OnStreamChangedHookParam.MediaTrack> tracks = param.getTracks();
|
||||
|
@ -343,7 +347,7 @@ public class ZLMHttpHookListener {
|
|||
}
|
||||
}
|
||||
}else if ("broadcast".equals(param.getApp())){
|
||||
// 语音对讲推流 stream需要满足格式deviceId_channelId
|
||||
// 语音喊话推流 stream需要满足格式deviceId_channelId
|
||||
if (param.isRegist() && param.getStream().indexOf("_") > 0) {
|
||||
String[] streamArray = param.getStream().split("_");
|
||||
if (streamArray.length == 2) {
|
||||
|
@ -359,53 +363,38 @@ public class ZLMHttpHookListener {
|
|||
if (sendRtpItem == null) {
|
||||
// TODO 可能数据错误,重新开启语音通道
|
||||
}else {
|
||||
String is_Udp = sendRtpItem.isTcp() ? "0" : "1";
|
||||
MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
|
||||
logger.info("rtp/{}开始向上级推流, 目标={}:{},SSRC={}", sendRtpItem.getStreamId(), sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc());
|
||||
Map<String, Object> sendParam = new HashMap<>(12);
|
||||
sendParam.put("vhost","__defaultVhost__");
|
||||
sendParam.put("app",sendRtpItem.getApp());
|
||||
sendParam.put("stream",sendRtpItem.getStreamId());
|
||||
sendParam.put("ssrc", sendRtpItem.getSsrc());
|
||||
sendParam.put("src_port", sendRtpItem.getLocalPort());
|
||||
sendParam.put("pt", sendRtpItem.getPt());
|
||||
sendParam.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0");
|
||||
sendParam.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0");
|
||||
|
||||
JSONObject jsonObject;
|
||||
if (sendRtpItem.isTcpActive()) {
|
||||
jsonObject = zlmrtpServerFactory.startSendRtpPassive(mediaInfo, sendParam);
|
||||
} else {
|
||||
sendParam.put("is_udp", is_Udp);
|
||||
sendParam.put("dst_url", sendRtpItem.getIp());
|
||||
sendParam.put("dst_port", sendRtpItem.getPort());
|
||||
jsonObject = zlmrtpServerFactory.startSendRtpStream(mediaInfo, sendParam);
|
||||
}
|
||||
if (jsonObject != null && jsonObject.getInteger("code") == 0) {
|
||||
logger.info("[语音对讲] 自动推流成功, device: {}, channel: {}", deviceId, channelId);
|
||||
JSONObject jsonObject = zlmrtpServerFactory.startSendRtp(mediaInfo, sendRtpItem);
|
||||
if (jsonObject != null && jsonObject.getInteger("code") == 0 ) {
|
||||
logger.info("[语音喊话] 自动推流成功, device: {}, channel: {}", device.getDeviceId(), channelId);
|
||||
}else {
|
||||
logger.info("[语音对讲] 推流失败, 结果: {}", jsonObject);
|
||||
logger.info("[语音喊话] 推流失败, 结果: {}", jsonObject);
|
||||
}
|
||||
|
||||
}
|
||||
}else {
|
||||
// 开启语音对讲通道
|
||||
// 开启语音喊话通道
|
||||
try {
|
||||
playService.audioBroadcastCmd(device, channelId, 60, (msg)->{
|
||||
logger.info("[语音对讲] 通道建立成功, device: {}, channel: {}", deviceId, channelId);
|
||||
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());
|
||||
logger.error("[命令发送失败] 语音喊话: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}else {
|
||||
logger.info("[语音喊话] 推流指向的·通道{}未找到", channelId);
|
||||
}
|
||||
}else {
|
||||
logger.info("[语音喊话] 推流指向的·设备{}未找到", deviceId);
|
||||
}
|
||||
}else {
|
||||
logger.info("[语音喊话] 推流格式有误, 格式为: broadcast/设备编号_通道编号 ");
|
||||
}
|
||||
}
|
||||
|
||||
}else if ("talk".equals(param.getApp())){
|
||||
// 语音对讲推流 stream需要满足格式deviceId_channelId
|
||||
// 语音喊话推流 stream需要满足格式deviceId_channelId
|
||||
if (param.isRegist() && param.getStream().indexOf("_") > 0) {
|
||||
String[] streamArray = param.getStream().split("_");
|
||||
if (streamArray.length == 2) {
|
||||
|
@ -421,33 +410,11 @@ public class ZLMHttpHookListener {
|
|||
if (sendRtpItem == null) {
|
||||
// TODO 可能数据错误,重新开启语音通道
|
||||
}else {
|
||||
MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
|
||||
logger.info("rtp/{}开始向上级推流, 目标={}:{},SSRC={}", sendRtpItem.getStreamId(), sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc());
|
||||
Map<String, Object> sendParam = new HashMap<>(12);
|
||||
sendParam.put("vhost","__defaultVhost__");
|
||||
sendParam.put("app",sendRtpItem.getApp());
|
||||
sendParam.put("stream",sendRtpItem.getStreamId());
|
||||
sendParam.put("ssrc", sendRtpItem.getSsrc());
|
||||
sendParam.put("src_port", sendRtpItem.getLocalPort());
|
||||
sendParam.put("pt", sendRtpItem.getPt());
|
||||
sendParam.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0");
|
||||
sendParam.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0");
|
||||
|
||||
JSONObject jsonObject;
|
||||
if (sendRtpItem.isTcpActive()) {
|
||||
jsonObject = zlmrtpServerFactory.startSendRtpPassive(mediaInfo, sendParam);
|
||||
} else {
|
||||
sendParam.put("is_udp", sendRtpItem.isTcp() ? "0" : "1");
|
||||
sendParam.put("dst_url", sendRtpItem.getIp());
|
||||
sendParam.put("dst_port", sendRtpItem.getPort());
|
||||
jsonObject = zlmrtpServerFactory.startSendRtpStream(mediaInfo, sendParam);
|
||||
}
|
||||
if (jsonObject != null && jsonObject.getInteger("code") == 0) {
|
||||
logger.info("[语音对讲] 自动推流成功, device: {}, channel: {}", deviceId, channelId);
|
||||
}
|
||||
zlmrtpServerFactory.startSendRtp(mediaInfo, sendRtpItem);
|
||||
}
|
||||
}else {
|
||||
// 开启语音对讲通道
|
||||
// 开启语音喊话通道
|
||||
MediaServerItem mediaServerItem = mediaServerService.getOne(param.getMediaServerId());
|
||||
playService.talk(mediaServerItem, device, channelId, (mediaServer, jsonObject)->{
|
||||
System.out.println("开始推流");
|
||||
|
@ -549,9 +516,7 @@ public class ZLMHttpHookListener {
|
|||
}
|
||||
}
|
||||
|
||||
JSONObject ret = new JSONObject();
|
||||
ret.put("code", 0);
|
||||
ret.put("msg", "success");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -348,4 +348,52 @@ public class ZLMRTPServerFactory {
|
|||
return result;
|
||||
}
|
||||
|
||||
public JSONObject startSendRtp(MediaServerItem mediaInfo, SendRtpItem sendRtpItem) {
|
||||
String is_Udp = sendRtpItem.isTcp() ? "0" : "1";
|
||||
logger.info("rtp/{}开始向上级推流, 目标={}:{},SSRC={}", sendRtpItem.getStreamId(), 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.getStreamId());
|
||||
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) {
|
||||
HookSubscribeForRtpServerTimeout hookSubscribeForRtpServerTimeout = HookSubscribeFactory.on_rtp_server_timeout(sendRtpItem.getSsrc(), null, mediaInfo.getId());
|
||||
hookSubscribe.removeSubscribe(hookSubscribeForRtpServerTimeout);
|
||||
if (releasePort(mediaInfo, sendRtpItem.getSsrc())) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
package com.genersoft.iot.vmp.service;
|
||||
|
||||
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
||||
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.github.pagehelper.PageInfo;
|
||||
|
||||
import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.SipException;
|
||||
import java.text.ParseException;
|
||||
|
||||
/**
|
||||
* 国标平台的业务类
|
||||
* @author lin
|
||||
|
@ -48,4 +57,23 @@ public interface IPlatformService {
|
|||
* @param platformId 平台
|
||||
*/
|
||||
void sendNotifyMobilePosition(String platformId);
|
||||
|
||||
/**
|
||||
* 向上级发送语音喊话的消息
|
||||
* @param platform 平台
|
||||
* @param channelId 通道
|
||||
* @param hookEvent hook事件
|
||||
* @param errorEvent 信令错误事件
|
||||
* @param timeoutCallback 超时事件
|
||||
*/
|
||||
void broadcastInvite(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem, ZlmHttpHookSubscribe.Event hookEvent,
|
||||
SipSubscribe.Event errorEvent, InviteTimeOutCallback timeoutCallback) throws InvalidArgumentException, ParseException, SipException;
|
||||
|
||||
/**
|
||||
* 语音喊话回复BYE
|
||||
* @param platform 平台
|
||||
* @param channelId 通道
|
||||
* @param stream 流信息
|
||||
*/
|
||||
void stopBroadcast(ParentPlatform platform, String channelId, String stream )throws InvalidArgumentException, ParseException, SsrcTransactionNotFoundException, SipException;
|
||||
}
|
||||
|
|
|
@ -53,10 +53,13 @@ public interface IPlayService {
|
|||
|
||||
void zlmServerOnline(String mediaServerId);
|
||||
|
||||
AudioBroadcastResult audioBroadcast(Device device, String channelId);
|
||||
void stopAudioBroadcast(String deviceId, String channelId);
|
||||
AudioBroadcastResult audioBroadcastInfo(Device device, String channelId);
|
||||
|
||||
void audioBroadcastCmd(Device device, String channelId, int timeout, AudioBroadcastEvent event) throws InvalidArgumentException, ParseException, SipException;
|
||||
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;
|
||||
|
||||
|
|
|
@ -1,15 +1,25 @@
|
|||
package com.genersoft.iot.vmp.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.genersoft.iot.vmp.common.StreamInfo;
|
||||
import com.genersoft.iot.vmp.conf.DynamicTask;
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
||||
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
|
||||
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
|
||||
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.dto.MediaServerItem;
|
||||
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.service.bean.GPSMsgInfo;
|
||||
import com.genersoft.iot.vmp.service.bean.InviteTimeOutCallback;
|
||||
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
import com.genersoft.iot.vmp.storager.dao.GbStreamMapper;
|
||||
import com.genersoft.iot.vmp.storager.dao.ParentPlatformMapper;
|
||||
|
@ -21,11 +31,13 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.ResponseEvent;
|
||||
import javax.sip.SipException;
|
||||
import java.text.ParseException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author lin
|
||||
|
@ -65,6 +77,16 @@ public class PlatformServiceImpl implements IPlatformService {
|
|||
@Autowired
|
||||
private UserSetting userSetting;
|
||||
|
||||
@Autowired
|
||||
private ZlmHttpHookSubscribe subscribe;
|
||||
|
||||
@Autowired
|
||||
private VideoStreamSessionManager streamSession;
|
||||
|
||||
|
||||
@Autowired
|
||||
private IPlayService playService;
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
|
@ -295,4 +317,137 @@ public class PlatformServiceImpl implements IPlatformService {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void broadcastInvite(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem, ZlmHttpHookSubscribe.Event hookEvent,
|
||||
SipSubscribe.Event errorEvent, InviteTimeOutCallback timeoutCallback) throws InvalidArgumentException, ParseException, SipException {
|
||||
|
||||
if (mediaServerItem == null) {
|
||||
logger.info("[国标级联] 语音喊话未找到可用的zlm. platform: {}", platform.getServerGBId());
|
||||
return;
|
||||
}
|
||||
StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(platform.getServerGBId(), channelId);
|
||||
if (streamInfo != null) {
|
||||
// 如果zlm不存在这个流,则删除数据即可
|
||||
MediaServerItem mediaServerItemForStreamInfo = mediaServerService.getOne(streamInfo.getMediaServerId());
|
||||
if (mediaServerItemForStreamInfo != null) {
|
||||
Boolean ready = zlmrtpServerFactory.isStreamReady(mediaServerItemForStreamInfo, streamInfo.getApp(), streamInfo.getStream());
|
||||
if (!ready) {
|
||||
// 错误存在于redis中的数据
|
||||
redisCatchStorage.stopPlay(streamInfo);
|
||||
}else {
|
||||
// 流确实尚在推流,直接回调结果
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("app", streamInfo.getApp());
|
||||
json.put("stream", streamInfo.getStream());
|
||||
hookEvent.response(mediaServerItemForStreamInfo, json);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String streamId = null;
|
||||
if (mediaServerItem.isRtpEnable()) {
|
||||
streamId = String.format("%s_%s", platform.getServerGBId(), channelId);
|
||||
}
|
||||
// 默认不进行SSRC校验, TODO 后续可改为配置
|
||||
boolean ssrcCheck = false;
|
||||
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, ssrcCheck, false);
|
||||
if (ssrcInfo == null || ssrcInfo.getPort() < 0) {
|
||||
logger.info("[国标级联] 发起语音喊话 开启端口监听失败, platform: {}, channel: {}", platform.getServerGBId(), channelId);
|
||||
errorEvent.response(new SipSubscribe.EventResult(-1, "端口监听失败"));
|
||||
return;
|
||||
}
|
||||
logger.info("[国标级联] 发起语音喊话 deviceId: {}, channelId: {},收流端口: {}, 收流模式:{}, SSRC: {}, SSRC校验:{}",
|
||||
platform.getServerGBId(), channelId, ssrcInfo.getPort(), userSetting.getBroadcastForPlatform(), ssrcInfo.getSsrc(), ssrcCheck);
|
||||
|
||||
String timeOutTaskKey = UUID.randomUUID().toString();
|
||||
dynamicTask.startDelay(timeOutTaskKey, () -> {
|
||||
// 执行超时任务时查询是否已经成功,成功了则不执行超时任务,防止超时任务取消失败的情况
|
||||
if (redisCatchStorage.queryPlayByDevice(platform.getServerGBId(), channelId) == 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, response)->{
|
||||
dynamicTask.stop(timeOutTaskKey);
|
||||
// hook响应
|
||||
playService.onPublishHandlerForPlay(mediaServerItemForInvite, response, platform.getServerGBId(), channelId);
|
||||
// 收到流
|
||||
if (hookEvent != null) {
|
||||
hookEvent.response(mediaServerItem, response);
|
||||
}
|
||||
}, event -> {
|
||||
// 收到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) {
|
||||
return;
|
||||
}
|
||||
logger.info("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse);
|
||||
if (!mediaServerItem.isRtpEnable()) {
|
||||
logger.info("[点播消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
|
||||
|
||||
if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) {
|
||||
// ssrc 不可用
|
||||
// 释放ssrc
|
||||
mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
|
||||
streamSession.remove(platform.getServerGBId(), channelId, ssrcInfo.getStream());
|
||||
event.msg = "下级自定义了ssrc,但是此ssrc不可用";
|
||||
event.statusCode = 400;
|
||||
errorEvent.response(event);
|
||||
return;
|
||||
}
|
||||
|
||||
// 单端口模式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, (MediaServerItem mediaServerItemInUse, JSONObject response) -> {
|
||||
logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString());
|
||||
dynamicTask.stop(timeOutTaskKey);
|
||||
// hook响应
|
||||
playService.onPublishHandlerForPlay(mediaServerItemInUse, response, platform.getServerGBId(), channelId);
|
||||
hookEvent.response(mediaServerItemInUse, response);
|
||||
});
|
||||
}
|
||||
// 关闭rtp server
|
||||
mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
|
||||
// 重新开启ssrc server
|
||||
mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, false, false, ssrcInfo.getPort());
|
||||
|
||||
}
|
||||
}
|
||||
}, eventResult -> {
|
||||
// 收到错误回复
|
||||
if (errorEvent != null) {
|
||||
errorEvent.response(eventResult);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopBroadcast(ParentPlatform platform, String channelId, String stream) throws InvalidArgumentException, ParseException, SsrcTransactionNotFoundException, SipException {
|
||||
commanderForPlatform.streamByeCmd(platform, channelId, stream, null, null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -987,7 +987,7 @@ public class PlayServiceImpl implements IPlayService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public AudioBroadcastResult audioBroadcast(Device device, String channelId) {
|
||||
public AudioBroadcastResult audioBroadcastInfo(Device device, String channelId) {
|
||||
if (device == null || channelId == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -1012,46 +1012,51 @@ public class PlayServiceImpl implements IPlayService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void audioBroadcastCmd(Device device, String channelId, int timeout, AudioBroadcastEvent event) throws InvalidArgumentException, ParseException, SipException {
|
||||
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;
|
||||
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;
|
||||
return false;
|
||||
}
|
||||
// 查询通道使用状态
|
||||
if (audioBroadcastManager.exit(device.getDeviceId(), channelId)) {
|
||||
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, null, null);
|
||||
if (sendRtpItem != null && sendRtpItem.isOnlyAudio()) {
|
||||
// 查询流是否存在,不存在则认为是异常状态
|
||||
MediaServerItem mediaServerItem = mediaServerService.getOne(sendRtpItem.getMediaServerId());
|
||||
Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, sendRtpItem.getApp(), sendRtpItem.getStreamId());
|
||||
if (streamReady) {
|
||||
logger.warn("语音广播已经开启: {}", channelId);
|
||||
event.call("语音广播已经开启");
|
||||
return;
|
||||
} else {
|
||||
audioBroadcastManager.del(deviceChannel.getDeviceId(), channelId);
|
||||
redisCatchStorage.deleteSendRTPServer(device.getDeviceId(), channelId, sendRtpItem.getCallId(), sendRtpItem.getStreamId());
|
||||
}
|
||||
}
|
||||
if (audioBroadcastInUse(device, channelId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 发送通知
|
||||
cmder.audioBroadcastCmd(device, channelId, eventResultForOk -> {
|
||||
// 发送成功
|
||||
AudioBroadcastCatch audioBroadcastCatch = new AudioBroadcastCatch(device.getDeviceId(), channelId, AudioBroadcastCatchStatus.Ready);
|
||||
audioBroadcastManager.add(audioBroadcastCatch);
|
||||
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 = zlmrtpServerFactory.isStreamReady(mediaServerServiceOne, sendRtpItem.getApp(), sendRtpItem.getStreamId());
|
||||
if (streamReady) {
|
||||
logger.warn("语音广播通道使用中: {}", channelId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1075,6 +1080,9 @@ public class PlayServiceImpl implements IPlayService {
|
|||
param.put("stream", sendRtpItem.getStreamId());
|
||||
zlmresTfulUtils.stopSendRtp(mediaInfo, param);
|
||||
}
|
||||
if (audioBroadcastCatch.isFromPlatform()) {
|
||||
// TODO 向上级发送BYE结束语音喊话
|
||||
}
|
||||
|
||||
audioBroadcastManager.del(deviceId, channelId);
|
||||
}
|
||||
|
|
|
@ -267,7 +267,7 @@ public class PlayController {
|
|||
throw new ControllerException(ErrorCode.ERROR400.getCode(), "未找到通道: " + channelId);
|
||||
}
|
||||
|
||||
return playService.audioBroadcast(device, channelId);
|
||||
return playService.audioBroadcastInfo(device, channelId);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -168,7 +168,7 @@ user-settings:
|
|||
# 保存移动位置历史轨迹:true:保留历史数据,false:仅保留最后的位置(默认)
|
||||
save-position-history: false
|
||||
# 点播等待超时时间,单位:毫秒
|
||||
play-timeout: 3000
|
||||
play-timeout: 18000
|
||||
# 上级点播等待超时时间,单位:毫秒
|
||||
platform-play-timeout: 60000
|
||||
# 是否开启接口鉴权
|
||||
|
@ -195,6 +195,8 @@ user-settings:
|
|||
gb-send-stream-strict: false
|
||||
# 设备上线时是否自动同步通道
|
||||
sync-channel-on-device-online: false
|
||||
# 国标级联语音喊话发流模式 * UDP:udp传输 TCP-ACTIVE:tcp主动模式 TCP-PASSIVE:tcp被动模式
|
||||
broadcast-for-platform: UDP
|
||||
|
||||
# 关闭在线文档(生产环境建议关闭)
|
||||
springdoc:
|
||||
|
|
|
@ -279,7 +279,7 @@
|
|||
</div>
|
||||
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="语音对讲" name="broadcast" >
|
||||
<el-tab-pane label="语音喊话" name="broadcast" >
|
||||
<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>
|
||||
|
@ -854,7 +854,7 @@ export default {
|
|||
if (this.broadcastStatus == -1) {
|
||||
// 默认状态, 开始
|
||||
this.broadcastStatus = 0
|
||||
// 发起语音对讲
|
||||
// 发起语音喊话
|
||||
this.$axios({
|
||||
method: 'get',
|
||||
url: '/api/play/broadcast/' + this.deviceId + '/' + this.channelId + "?timeout=30"
|
||||
|
@ -897,7 +897,7 @@ export default {
|
|||
let pushKey = res.data.data.pushKey;
|
||||
// 获取推流鉴权KEY
|
||||
url += "&sign=" + crypto.createHash('md5').update(pushKey, "utf8").digest('hex')
|
||||
console.log("开始语音对讲: " + url)
|
||||
console.log("开始语音喊话: " + url)
|
||||
this.broadcastRtc = new ZLMRTCClient.Endpoint({
|
||||
debug: true, // 是否打印日志
|
||||
zlmsdpUrl: url, //流地址
|
||||
|
@ -923,7 +923,7 @@ export default {
|
|||
console.error('不支持webrtc',e)
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: '不支持webrtc, 无法进行语音对讲',
|
||||
message: '不支持webrtc, 无法进行语音喊话',
|
||||
type: 'error'
|
||||
});
|
||||
this.broadcastStatus = -1;
|
||||
|
|
Loading…
Reference in New Issue