添加第二种语音对讲实现
This commit is contained in:
@@ -2,6 +2,7 @@ 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.stack.SIPDialog;
|
||||
|
||||
import javax.sip.Dialog;
|
||||
@@ -40,12 +41,7 @@ public class AudioBroadcastCatch {
|
||||
/**
|
||||
* 请求信息
|
||||
*/
|
||||
private SIPRequest request;
|
||||
|
||||
/**
|
||||
* 会话信息
|
||||
*/
|
||||
private SIPDialog dialog;
|
||||
private SipTransactionInfo sipTransactionInfo;
|
||||
|
||||
|
||||
public String getDeviceId() {
|
||||
@@ -72,19 +68,15 @@ public class AudioBroadcastCatch {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public void setDialog(SIPDialog dialog) {
|
||||
this.dialog = dialog;
|
||||
public SipTransactionInfo getSipTransactionInfo() {
|
||||
return sipTransactionInfo;
|
||||
}
|
||||
|
||||
public SIPDialog getDialog() {
|
||||
return dialog;
|
||||
public void setSipTransactionInfo(SipTransactionInfo sipTransactionInfo) {
|
||||
this.sipTransactionInfo = sipTransactionInfo;
|
||||
}
|
||||
|
||||
public SIPRequest getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
public void setRequest(SIPRequest request) {
|
||||
this.request = request;
|
||||
public void setSipTransactionInfoByRequset(SIPResponse response) {
|
||||
this.sipTransactionInfo = new SipTransactionInfo(response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.genersoft.iot.vmp.gb28181.bean;
|
||||
|
||||
public enum InviteStreamType {
|
||||
|
||||
PLAY,PLAYBACK,PUSH,PROXY,CLOUD_RECORD_PUSH,CLOUD_RECORD_PROXY
|
||||
PLAY,PLAYBACK,PUSH,PROXY,CLOUD_RECORD_PUSH,CLOUD_RECORD_PROXY,TALK
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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;
|
||||
@@ -57,7 +58,7 @@ public class SipSubscribe {
|
||||
logger.debug("errorSubscribes.size:{}",errorSubscribes.size());
|
||||
}
|
||||
|
||||
public interface Event { void response(EventResult eventResult) ;
|
||||
public interface Event { void response(EventResult eventResult);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,18 +8,12 @@ import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
|
||||
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
|
||||
import gov.nist.javax.sip.message.SIPRequest;
|
||||
import gov.nist.javax.sip.message.SIPRequest;
|
||||
import gov.nist.javax.sip.stack.SIPDialog;
|
||||
|
||||
import javax.sip.Dialog;
|
||||
import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.SipException;
|
||||
import java.text.ParseException;
|
||||
import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.PeerUnavailableException;
|
||||
import javax.sip.SipException;
|
||||
import javax.sip.message.Request;
|
||||
import java.text.ParseException;
|
||||
import javax.sip.message.Request;
|
||||
|
||||
/**
|
||||
* @description:设备能力接口,用于定义设备的控制、查询能力
|
||||
@@ -130,14 +124,18 @@ public interface ISIPCommander {
|
||||
String startTime, String endTime, int downloadSpeed, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
|
||||
SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
|
||||
|
||||
|
||||
/**
|
||||
* 视频流停止
|
||||
*/
|
||||
void streamByeCmd(String deviceId, String channelId, String stream, String callId, SipSubscribe.Event okEvent);
|
||||
void streamByeCmd(String deviceId, String channelId, String stream, String callId);
|
||||
void streamByeCmd(Device device, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException;
|
||||
|
||||
void talkStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, String callId, ZlmHttpHookSubscribe.Event event, ZlmHttpHookSubscribe.Event eventForPush, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
|
||||
|
||||
void streamByeCmd(Device device, String channelId, String stream, String callId) throws InvalidArgumentException, ParseException, SipException, SsrcTransactionNotFoundException;
|
||||
|
||||
void streamByeCmd(Device device, String channelId, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException;
|
||||
|
||||
/**
|
||||
* 回放暂停
|
||||
*/
|
||||
@@ -168,22 +166,12 @@ public interface ISIPCommander {
|
||||
|
||||
|
||||
/**
|
||||
|
||||
/**
|
||||
* 语音广播
|
||||
*
|
||||
* @param device 视频设备
|
||||
*/
|
||||
boolean audioBroadcastCmd(Device device, String channelId, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent);
|
||||
void audioBroadcastCmd(Device device,String channelId);
|
||||
|
||||
/**
|
||||
* /**
|
||||
* 语音广播
|
||||
*
|
||||
* @param device 视频设备
|
||||
* @param device 视频设备
|
||||
*/
|
||||
void audioBroadcastCmd(Device device, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
|
||||
void audioBroadcastCmd(Device device) throws InvalidArgumentException, SipException, ParseException;
|
||||
void audioBroadcastCmd(Device device, String channelId, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
|
||||
|
||||
/**
|
||||
* 音视频录像控制
|
||||
|
||||
@@ -326,4 +326,37 @@ public class SIPRequestHeaderProvider {
|
||||
|
||||
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.createAddressFactory().createSipURI(channelId, device.getHostAddress());
|
||||
// via
|
||||
ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
|
||||
ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(sipConfig.getIp(), sipConfig.getPort(), device.getTransport(), viaTag);
|
||||
viaHeader.setRPort();
|
||||
viaHeaders.add(viaHeader);
|
||||
// from
|
||||
SipURI fromSipURI = sipFactory.createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
|
||||
Address fromAddress = sipFactory.createAddressFactory().createAddress(fromSipURI);
|
||||
FromHeader fromHeader = sipFactory.createHeaderFactory().createFromHeader(fromAddress, fromTag);
|
||||
// to
|
||||
SipURI toSipURI = sipFactory.createAddressFactory().createSipURI(channelId, device.getHostAddress());
|
||||
Address toAddress = sipFactory.createAddressFactory().createAddress(toSipURI);
|
||||
ToHeader toHeader = sipFactory.createHeaderFactory().createToHeader(toAddress, toTag);
|
||||
|
||||
// Forwards
|
||||
MaxForwardsHeader maxForwards = sipFactory.createHeaderFactory().createMaxForwardsHeader(70);
|
||||
// ceq
|
||||
CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.MESSAGE);
|
||||
|
||||
ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
|
||||
|
||||
request = sipFactory.createMessageFactory().createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader,
|
||||
toHeader, viaHeaders, maxForwards, contentTypeHeader, content);
|
||||
|
||||
request.addHeader(SipUtils.createUserAgentHeader(sipFactory, gitUtil));
|
||||
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.genersoft.iot.vmp.common.StreamInfo;
|
||||
import com.genersoft.iot.vmp.conf.DynamicTask;
|
||||
import com.genersoft.iot.vmp.conf.SipConfig;
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
||||
@@ -12,45 +11,32 @@ import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
|
||||
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.HookSubscribeForStreamPush;
|
||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
|
||||
import com.genersoft.iot.vmp.service.IMediaServerService;
|
||||
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
|
||||
import com.genersoft.iot.vmp.utils.GitUtil;
|
||||
import gov.nist.javax.sip.SIPConstants;
|
||||
import gov.nist.javax.sip.SipProviderImpl;
|
||||
import gov.nist.javax.sip.SipStackImpl;
|
||||
import gov.nist.javax.sip.message.SIPRequest;
|
||||
import gov.nist.javax.sip.message.SIPResponse;
|
||||
import gov.nist.javax.sip.stack.SIPClientTransaction;
|
||||
import gov.nist.javax.sip.stack.SIPClientTransactionImpl;
|
||||
import gov.nist.javax.sip.stack.SIPDialog;
|
||||
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.context.annotation.DependsOn;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import javax.sip.*;
|
||||
import javax.sip.address.Address;
|
||||
import javax.sip.address.SipURI;
|
||||
import javax.sip.header.*;
|
||||
import javax.sip.message.Request;
|
||||
import javax.sip.message.Response;
|
||||
import java.lang.reflect.Field;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description:设备能力接口,用于定义设备的控制、查询能力
|
||||
@@ -98,6 +84,9 @@ public class SIPCommander implements ISIPCommander {
|
||||
@Autowired
|
||||
private IMediaServerService mediaServerService;
|
||||
|
||||
@Autowired
|
||||
private ZLMRTPServerFactory zlmrtpServerFactory;
|
||||
|
||||
|
||||
/**
|
||||
* 云台方向放控制,使用配置文件中的默认镜头移动速度
|
||||
@@ -591,11 +580,73 @@ public class SIPCommander implements ISIPCommander {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void talkStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, String callId, ZlmHttpHookSubscribe.Event event, ZlmHttpHookSubscribe.Event eventForPush, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
|
||||
|
||||
String stream = ssrcInfo.getStream();
|
||||
|
||||
if (device == null) {
|
||||
return;
|
||||
}
|
||||
if (!mediaServerItem.isRtpEnable()) {
|
||||
// 单端口暂不支持语音对讲
|
||||
logger.info("[语音对讲] 单端口暂不支持此操作");
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
event.response(mediaServerItemInUse, json);
|
||||
subscribe.removeSubscribe(hookSubscribeForStreamChange);
|
||||
}
|
||||
});
|
||||
|
||||
CallIdHeader callIdHeader = device.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
|
||||
: udpSipProvider.getNewCallId();
|
||||
callIdHeader.setCallId(callId);
|
||||
HookSubscribeForStreamPush hookSubscribeForStreamPush = HookSubscribeFactory.on_publish("rtp", stream, null, mediaServerItem.getId());
|
||||
subscribe.addSubscribe(hookSubscribeForStreamPush, (MediaServerItem mediaServerItemInUse, JSONObject json) -> {
|
||||
if (eventForPush != null) {
|
||||
eventForPush.response(mediaServerItemInUse, json);
|
||||
}
|
||||
});
|
||||
//
|
||||
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 " + ssrcInfo.getPort() + " RTP/AVP 8\r\n");
|
||||
content.append("a=sendrecv\r\n");
|
||||
content.append("a=rtpmap:8 PCMA/8000\r\n");
|
||||
|
||||
content.append("y=" + ssrcInfo.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, ssrcInfo.getSsrc(), callIdHeader);
|
||||
transmitRequest(device.getTransport(), request, (e -> {
|
||||
streamSession.remove(device.getDeviceId(), 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(device.getDeviceId(), channelId, "talk", stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.play);
|
||||
okEvent.response(e);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频流停止, 不使用回调
|
||||
*/
|
||||
@Override
|
||||
public void streamByeCmd(Device device, String channelId, String stream, String callId) throws InvalidArgumentException, ParseException, SipException, SsrcTransactionNotFoundException {
|
||||
public synchronized void streamByeCmd(Device device, String channelId, String stream, String callId) throws InvalidArgumentException, ParseException, SipException, SsrcTransactionNotFoundException {
|
||||
streamByeCmd(device, channelId, stream, callId, null);
|
||||
}
|
||||
|
||||
@@ -603,7 +654,7 @@ public class SIPCommander implements ISIPCommander {
|
||||
* 视频流停止
|
||||
*/
|
||||
@Override
|
||||
public void streamByeCmd(Device device, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException {
|
||||
public synchronized void streamByeCmd(Device device, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException {
|
||||
SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, callId, stream);
|
||||
if (ssrcTransaction == null) {
|
||||
throw new SsrcTransactionNotFoundException(device.getDeviceId(), channelId, callId, stream);
|
||||
@@ -617,67 +668,34 @@ public class SIPCommander implements ISIPCommander {
|
||||
transmitRequest(device.getTransport(), byteRequest, null, okEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
@Override
|
||||
public synchronized void streamByeCmd(Device device, String channelId, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException {
|
||||
Request byteRequest = headerProvider.createByteRequest(device, channelId, sipTransactionInfo);
|
||||
transmitRequest(device.getTransport(), byteRequest, null, okEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 语音广播
|
||||
*
|
||||
* @param device 视频设备
|
||||
*/
|
||||
@Override
|
||||
public void audioBroadcastCmd(Device device) throws InvalidArgumentException, SipException, ParseException {
|
||||
|
||||
StringBuffer broadcastXml = new StringBuffer(200);
|
||||
String charset = device.getCharset();
|
||||
broadcastXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
|
||||
broadcastXml.append("<Notify>\r\n");
|
||||
broadcastXml.append("<CmdType>Broadcast</CmdType>\r\n");
|
||||
broadcastXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
|
||||
broadcastXml.append("<SourceID>" + sipConfig.getId() + "</SourceID>\r\n");
|
||||
broadcastXml.append("<TargetID>" + device.getDeviceId() + "</TargetID>\r\n");
|
||||
broadcastXml.append("</Notify>\r\n");
|
||||
/**
|
||||
* 语音广播
|
||||
*
|
||||
* @param device 视频设备
|
||||
*/
|
||||
@Override
|
||||
public boolean audioBroadcastCmd(Device device,String channelId, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) {
|
||||
try {
|
||||
StringBuffer broadcastXml = new StringBuffer(200);
|
||||
String charset = device.getCharset();
|
||||
broadcastXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
|
||||
broadcastXml.append("<Notify>\r\n");
|
||||
broadcastXml.append("<CmdType>Broadcast</CmdType>\r\n");
|
||||
broadcastXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
|
||||
broadcastXml.append("<SourceID>" + sipConfig.getId() + "</SourceID>\r\n");
|
||||
broadcastXml.append("<TargetID>" + channelId + "</TargetID>\r\n");
|
||||
broadcastXml.append("</Notify>\r\n");
|
||||
|
||||
CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
|
||||
: udpSipProvider.getNewCallId();
|
||||
|
||||
Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
|
||||
transmitRequest(device.getTransport(), request);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void audioBroadcastCmd(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
|
||||
|
||||
public void audioBroadcastCmd(Device device, String channelId, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
|
||||
StringBuffer broadcastXml = new StringBuffer(200);
|
||||
String charset = device.getCharset();
|
||||
broadcastXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
|
||||
broadcastXml.append("<Notify>\r\n");
|
||||
broadcastXml.append("<CmdType>Broadcast</CmdType>\r\n");
|
||||
broadcastXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</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("<TargetID>" + device.getDeviceId() + "</TargetID>\r\n");
|
||||
broadcastXml.append("<TargetID>" + channelId + "</TargetID>\r\n");
|
||||
broadcastXml.append("</Notify>\r\n");
|
||||
|
||||
CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
|
||||
: udpSipProvider.getNewCallId();
|
||||
|
||||
Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
|
||||
transmitRequest(device.getTransport(), request, errorEvent);
|
||||
transmitRequest(device.getTransport(), request, errorEvent, okEvent);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -676,7 +676,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void streamByeCmd(ParentPlatform platform, SendRtpItem sendRtpItem) throws SipException, InvalidArgumentException, ParseException {
|
||||
public synchronized void streamByeCmd(ParentPlatform platform, SendRtpItem sendRtpItem) throws SipException, InvalidArgumentException, ParseException {
|
||||
if (sendRtpItem == null ) {
|
||||
logger.info("[向上级发送BYE], sendRtpItem 为NULL");
|
||||
return;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package com.genersoft.iot.vmp.gb28181.transmit.event.request;
|
||||
|
||||
import com.genersoft.iot.vmp.conf.SipConfig;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||
import gov.nist.javax.sip.SipProviderImpl;
|
||||
import gov.nist.javax.sip.SipStackImpl;
|
||||
import gov.nist.javax.sip.message.SIPRequest;
|
||||
import gov.nist.javax.sip.message.SIPResponse;
|
||||
import gov.nist.javax.sip.stack.SIPServerTransaction;
|
||||
import gov.nist.javax.sip.stack.SIPServerTransactionImpl;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.dom4j.Document;
|
||||
@@ -27,8 +27,6 @@ import javax.sip.message.MessageFactory;
|
||||
import javax.sip.message.Request;
|
||||
import javax.sip.message.Response;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -51,6 +49,9 @@ public abstract class SIPRequestProcessorParent {
|
||||
@Qualifier(value="udpSipProvider")
|
||||
private SipProviderImpl udpSipProvider;
|
||||
|
||||
@Autowired
|
||||
private SipConfig sipConfig;
|
||||
|
||||
/**
|
||||
* 根据 RequestEvent 获取 ServerTransaction
|
||||
* @param evt
|
||||
@@ -60,13 +61,15 @@ public abstract class SIPRequestProcessorParent {
|
||||
Request request = evt.getRequest();
|
||||
SIPServerTransactionImpl serverTransaction = (SIPServerTransactionImpl)evt.getServerTransaction();
|
||||
// 判断TCP还是UDP
|
||||
boolean isTcp = false;
|
||||
ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
|
||||
String transport = reqViaHeader.getTransport();
|
||||
if (transport.equalsIgnoreCase("TCP")) {
|
||||
isTcp = true;
|
||||
}
|
||||
if (serverTransaction != null && serverTransaction.getOriginalRequest() == null) {
|
||||
serverTransaction.setOriginalRequest((SIPRequest) evt.getRequest());
|
||||
}
|
||||
boolean isTcp = "TCP".equals(transport);
|
||||
|
||||
if (serverTransaction == null) {
|
||||
try {
|
||||
if (isTcp) {
|
||||
@@ -187,7 +190,6 @@ public abstract class SIPRequestProcessorParent {
|
||||
* 回复带sdp的200
|
||||
*/
|
||||
public SIPResponse responseSdpAck(ServerTransaction serverTransaction, String sdp, ParentPlatform platform) throws SipException, InvalidArgumentException, ParseException {
|
||||
|
||||
ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
|
||||
|
||||
// 兼容国标中的使用编码@域名作为RequestURI的情况
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.genersoft.iot.vmp.conf.DynamicTask;
|
||||
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.bean.ParentPlatform;
|
||||
@@ -14,6 +15,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorP
|
||||
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
|
||||
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.bean.RequestPushStreamMsg;
|
||||
import com.genersoft.iot.vmp.service.redisMsg.RedisGbPlayMsgListener;
|
||||
@@ -21,7 +23,6 @@ import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
|
||||
import gov.nist.javax.sip.message.SIPRequest;
|
||||
import gov.nist.javax.sip.stack.SIPDialog;
|
||||
import com.genersoft.iot.vmp.utils.SerializeUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
@@ -81,6 +82,9 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
|
||||
@Autowired
|
||||
private ISIPCommander cmder;
|
||||
|
||||
@Autowired
|
||||
private IDeviceService deviceService;
|
||||
|
||||
@Autowired
|
||||
private ISIPCommanderForPlatform commanderForPlatform;
|
||||
|
||||
@@ -106,7 +110,7 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
|
||||
// 取消设置的超时任务
|
||||
dynamicTask.stop(callIdHeader.getCallId());
|
||||
String channelId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser();
|
||||
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(platformGbId, channelId, null, callIdHeader.getCallId());
|
||||
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, null, callIdHeader.getCallId());
|
||||
if (sendRtpItem == null) {
|
||||
logger.warn("[收到ACK]:未找到通道({})的推流信息", channelId);
|
||||
return;
|
||||
@@ -123,7 +127,7 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
|
||||
param.put("pt", sendRtpItem.getPt());
|
||||
param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0");
|
||||
param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0");
|
||||
if (!sendRtpItem.isTcp() && parentPlatform.isRtcp()) {
|
||||
if (!sendRtpItem.isTcp() && parentPlatform != null && parentPlatform.isRtcp()) {
|
||||
// 开启rtcp保活
|
||||
param.put("udp_rtcp_timeout", "1");
|
||||
}
|
||||
@@ -141,29 +145,28 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
|
||||
if (jsonObject == null) {
|
||||
logger.error("RTP推流失败: 请检查ZLM服务");
|
||||
} else if (jsonObject.getInteger("code") == 0) {
|
||||
|
||||
if (sendRtpItem.isOnlyAudio()) {
|
||||
AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
|
||||
audioBroadcastCatch.setStatus(AudioBroadcastCatchStatus.Ok);
|
||||
audioBroadcastCatch.setDialog((SIPDialog) evt.getDialog());
|
||||
audioBroadcastCatch.setRequest((SIPRequest) evt.getRequest());
|
||||
audioBroadcastManager.update(audioBroadcastCatch);
|
||||
String waiteStreamTimeoutTaskKey = "waite-stream-" + audioBroadcastCatch.getDeviceId() + audioBroadcastCatch.getChannelId();
|
||||
dynamicTask.stop(waiteStreamTimeoutTaskKey);
|
||||
}
|
||||
logger.info("RTP推流成功[ {}/{} ],{}->{}:{}, ", param.get("app"), param.get("stream"), jsonObject.getString("local_port"), param.get("dst_url"), param.get("dst_port"));
|
||||
} else {
|
||||
logger.error("RTP推流失败: {}, 参数:{}", jsonObject.getString("msg"), JSONObject.toJSON(param));
|
||||
if (sendRtpItem.isOnlyAudio()) {
|
||||
// 语音对讲
|
||||
try {
|
||||
cmder.streamByeCmd((SIPDialog) evt.getDialog(), sendRtpItem.getChannelId(), (SIPRequest) evt.getRequest(), null);
|
||||
} catch (SipException | ParseException | InvalidArgumentException e) {
|
||||
logger.error("[命令发送失败] 停止语音对讲: {}", e.getMessage());
|
||||
Device device = deviceService.queryDevice(platformGbId);
|
||||
if (device != null) {
|
||||
try {
|
||||
cmder.streamByeCmd(device, sendRtpItem.getChannelId(), sendRtpItem.getStreamId(), null);
|
||||
} catch (SipException | ParseException | InvalidArgumentException |
|
||||
SsrcTransactionNotFoundException e) {
|
||||
logger.error("[命令发送失败] 停止语音对讲: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// 向上级平台
|
||||
commanderForPlatform.streamByeCmd(parentPlatform, callIdHeader.getCallId());
|
||||
try {
|
||||
commanderForPlatform.streamByeCmd(parentPlatform, callIdHeader.getCallId());
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
logger.error("[命令发送失败] 国标级联, 回复BYE: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
if (mediaInfo == null) {
|
||||
RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance(
|
||||
@@ -179,7 +182,6 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
private void startSendRtpStreamHand(RequestEvent evt, SendRtpItem sendRtpItem, ParentPlatform parentPlatform,
|
||||
|
||||
@@ -2,10 +2,8 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
|
||||
|
||||
import com.genersoft.iot.vmp.common.StreamInfo;
|
||||
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.InviteStreamType;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
|
||||
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.transmit.SIPProcessorObserver;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
|
||||
@@ -54,6 +52,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
|
||||
@Autowired
|
||||
private IDeviceService deviceService;
|
||||
|
||||
@Autowired
|
||||
private AudioBroadcastManager audioBroadcastManager;
|
||||
|
||||
@Autowired
|
||||
private IVideoManagerStorage storager;
|
||||
|
||||
@@ -91,78 +92,79 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
|
||||
logger.error("[回复BYE信息失败],{}", e.getMessage());
|
||||
}
|
||||
CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME);
|
||||
String platformGbId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(FromHeader.NAME)).getAddress().getURI()).getUser();
|
||||
String channelId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser();
|
||||
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(platformGbId, channelId, null, callIdHeader.getCallId());
|
||||
logger.info("[收到bye] {}/{}", platformGbId, channelId);
|
||||
if (sendRtpItem != null){
|
||||
String streamId = sendRtpItem.getStreamId();
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
param.put("vhost","__defaultVhost__");
|
||||
param.put("app",sendRtpItem.getApp());
|
||||
param.put("stream",streamId);
|
||||
param.put("ssrc",sendRtpItem.getSsrc());
|
||||
logger.info("[收到bye] 停止向上级推流:{}", streamId);
|
||||
MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
|
||||
redisCatchStorage.deleteSendRTPServer(platformGbId, channelId, callIdHeader.getCallId(), null);
|
||||
zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param);
|
||||
int totalReaderCount = zlmrtpServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId);
|
||||
if (totalReaderCount <= 0) {
|
||||
logger.info("[收到bye] {} 无其它观看者,通知设备停止推流", streamId);
|
||||
if (sendRtpItem.getPlayType().equals(InviteStreamType.PLAY)) {
|
||||
Device device = deviceService.queryDevice(sendRtpItem.getDeviceId());
|
||||
if (device == null) {
|
||||
logger.info("[收到bye] {} 通知设备停止推流时未找到设备信息", streamId);
|
||||
}
|
||||
try {
|
||||
logger.warn("[停止点播] {}/{}", sendRtpItem.getDeviceId(), channelId);
|
||||
cmder.streamByeCmd(device, channelId, streamId, null);
|
||||
} catch (InvalidArgumentException | ParseException | SipException |
|
||||
SsrcTransactionNotFoundException e) {
|
||||
logger.error("[收到bye] {} 无其它观看者,通知设备停止推流, 发送BYE失败 {}",streamId, e.getMessage());
|
||||
}
|
||||
}
|
||||
if (sendRtpItem.isOnlyAudio()) {
|
||||
playService.stopAudioBroadcast(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
|
||||
}
|
||||
if (sendRtpItem.getPlayType().equals(InviteStreamType.PUSH)) {
|
||||
MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
|
||||
sendRtpItem.getApp(), sendRtpItem.getStreamId(), sendRtpItem.getChannelId(),
|
||||
sendRtpItem.getPlatformId(), null, null, sendRtpItem.getMediaServerId());
|
||||
redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 可能是设备主动停止
|
||||
Device device = storager.queryVideoDeviceByChannelId(platformGbId);
|
||||
if (device != null) {
|
||||
storager.stopPlay(device.getDeviceId(), channelId);
|
||||
StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(device.getDeviceId(), channelId);
|
||||
if (streamInfo != null) {
|
||||
redisCatchStorage.stopPlay(streamInfo);
|
||||
mediaServerService.closeRTPServer(streamInfo.getMediaServerId(), streamInfo.getStream());
|
||||
}
|
||||
SsrcTransaction ssrcTransactionForPlay = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, "play", null);
|
||||
if (ssrcTransactionForPlay != null){
|
||||
if (ssrcTransactionForPlay.getCallId().equals(callIdHeader.getCallId())){
|
||||
// 释放ssrc
|
||||
MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransactionForPlay.getMediaServerId());
|
||||
if (mediaServerItem != null) {
|
||||
mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransactionForPlay.getSsrc());
|
||||
}
|
||||
streamSession.remove(device.getDeviceId(), channelId, ssrcTransactionForPlay.getStream());
|
||||
}
|
||||
}
|
||||
SsrcTransaction ssrcTransactionForPlayBack = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, callIdHeader.getCallId(), null);
|
||||
if (ssrcTransactionForPlayBack != null) {
|
||||
// 释放ssrc
|
||||
MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransactionForPlayBack.getMediaServerId());
|
||||
if (mediaServerItem != null) {
|
||||
mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransactionForPlayBack.getSsrc());
|
||||
}
|
||||
streamSession.remove(device.getDeviceId(), channelId, ssrcTransactionForPlayBack.getStream());
|
||||
}
|
||||
}
|
||||
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, null, callIdHeader.getCallId());
|
||||
|
||||
if (sendRtpItem != null){
|
||||
logger.info("[收到bye] {}/{}", sendRtpItem.getPlatformId(), sendRtpItem.getChannelId());
|
||||
String streamId = sendRtpItem.getStreamId();
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
param.put("vhost","__defaultVhost__");
|
||||
param.put("app",sendRtpItem.getApp());
|
||||
param.put("stream",streamId);
|
||||
param.put("ssrc",sendRtpItem.getSsrc());
|
||||
logger.info("[收到bye] 停止向上级推流:{}", streamId);
|
||||
MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
|
||||
redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(), callIdHeader.getCallId(), null);
|
||||
zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param);
|
||||
int totalReaderCount = zlmrtpServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId);
|
||||
if (totalReaderCount <= 0) {
|
||||
logger.info("[收到bye] {} 无其它观看者,通知设备停止推流", streamId);
|
||||
if (sendRtpItem.getPlayType().equals(InviteStreamType.PLAY)) {
|
||||
Device device = deviceService.queryDevice(sendRtpItem.getDeviceId());
|
||||
if (device == null) {
|
||||
logger.info("[收到bye] {} 通知设备停止推流时未找到设备信息", streamId);
|
||||
}
|
||||
try {
|
||||
logger.warn("[停止点播] {}/{}", sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
|
||||
cmder.streamByeCmd(device, sendRtpItem.getChannelId(), streamId, null);
|
||||
} catch (InvalidArgumentException | ParseException | SipException |
|
||||
SsrcTransactionNotFoundException e) {
|
||||
logger.error("[收到bye] {} 无其它观看者,通知设备停止推流, 发送BYE失败 {}",streamId, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (sendRtpItem.getPlayType().equals(InviteStreamType.PUSH)) {
|
||||
MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
|
||||
sendRtpItem.getApp(), sendRtpItem.getStreamId(), sendRtpItem.getChannelId(),
|
||||
sendRtpItem.getPlatformId(), null, null, sendRtpItem.getMediaServerId());
|
||||
redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel);
|
||||
}
|
||||
}
|
||||
playService.stopAudioBroadcast(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
|
||||
}
|
||||
|
||||
String platformGbId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(FromHeader.NAME)).getAddress().getURI()).getUser();
|
||||
String channelId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser();
|
||||
|
||||
// 可能是设备主动停止
|
||||
Device device = storager.queryVideoDeviceByChannelId(platformGbId);
|
||||
if (device != null) {
|
||||
storager.stopPlay(device.getDeviceId(), channelId);
|
||||
StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(device.getDeviceId(), channelId);
|
||||
if (streamInfo != null) {
|
||||
redisCatchStorage.stopPlay(streamInfo);
|
||||
mediaServerService.closeRTPServer(streamInfo.getMediaServerId(), streamInfo.getStream());
|
||||
}
|
||||
SsrcTransaction ssrcTransactionForPlay = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, "play", null);
|
||||
if (ssrcTransactionForPlay != null){
|
||||
if (ssrcTransactionForPlay.getCallId().equals(callIdHeader.getCallId())){
|
||||
// 释放ssrc
|
||||
MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransactionForPlay.getMediaServerId());
|
||||
if (mediaServerItem != null) {
|
||||
mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransactionForPlay.getSsrc());
|
||||
}
|
||||
streamSession.remove(device.getDeviceId(), channelId, ssrcTransactionForPlay.getStream());
|
||||
}
|
||||
}
|
||||
SsrcTransaction ssrcTransactionForPlayBack = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, callIdHeader.getCallId(), null);
|
||||
if (ssrcTransactionForPlayBack != null) {
|
||||
// 释放ssrc
|
||||
MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransactionForPlayBack.getMediaServerId());
|
||||
if (mediaServerItem != null) {
|
||||
mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransactionForPlayBack.getSsrc());
|
||||
}
|
||||
streamSession.remove(device.getDeviceId(), channelId, ssrcTransactionForPlayBack.getStream());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.genersoft.iot.vmp.conf.DynamicTask;
|
||||
import com.genersoft.iot.vmp.conf.SipConfig;
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
||||
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
|
||||
import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
|
||||
@@ -14,7 +13,8 @@ import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
|
||||
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.cmd.ISIPCommander;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||
@@ -30,20 +30,16 @@ import com.genersoft.iot.vmp.service.IStreamProxyService;
|
||||
import com.genersoft.iot.vmp.service.IStreamPushService;
|
||||
import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
|
||||
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
|
||||
import com.genersoft.iot.vmp.service.impl.RedisGbPlayMsgListener;
|
||||
import com.genersoft.iot.vmp.service.redisMsg.RedisGbPlayMsgListener;
|
||||
import com.genersoft.iot.vmp.service.redisMsg.RedisPushStreamResponseListener;
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
|
||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
||||
import com.genersoft.iot.vmp.utils.SerializeUtils;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
|
||||
import gov.nist.javax.sdp.TimeDescriptionImpl;
|
||||
import gov.nist.javax.sdp.fields.TimeField;
|
||||
import gov.nist.javax.sip.message.SIPRequest;
|
||||
import gov.nist.javax.sip.stack.SIPDialog;
|
||||
import gov.nist.javax.sip.message.SIPRequest;
|
||||
import gov.nist.javax.sip.message.SIPResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -72,7 +68,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
||||
private final String method = "INVITE";
|
||||
|
||||
@Autowired
|
||||
private SIPCommanderFroPlatform cmderFroPlatform;
|
||||
private ISIPCommanderForPlatform cmderFroPlatform;
|
||||
|
||||
@Autowired
|
||||
private IVideoManagerStorage storager;
|
||||
@@ -174,7 +170,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
||||
// 查询请求是否来自上级平台\设备
|
||||
ParentPlatform platform = storager.queryParentPlatByServerGBId(requesterId);
|
||||
if (platform == null) {
|
||||
inviteFromDeviceHandle(serverTransaction, requesterId);
|
||||
inviteFromDeviceHandle(serverTransaction, requesterId, channelId);
|
||||
} else {
|
||||
// 查询平台下是否有该通道
|
||||
DeviceChannel channel = storager.queryChannelInParentPlatform(requesterId, channelId);
|
||||
@@ -393,14 +389,15 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
||||
};
|
||||
SipSubscribe.Event errorEvent = ((event) -> {
|
||||
// 未知错误。直接转发设备点播的错误
|
||||
Response response = null;
|
||||
try {
|
||||
response = getMessageFactory().createResponse(event.statusCode, evt.getRequest());
|
||||
Response response = getMessageFactory().createResponse(event.statusCode, evt.getRequest());
|
||||
serverTransaction.sendResponse(response);
|
||||
System.out.println("未知错误。直接转发设备点播的错误");
|
||||
if (serverTransaction.getDialog() != null) {
|
||||
serverTransaction.getDialog().delete();
|
||||
}
|
||||
serverTransaction.getDialog().delete();
|
||||
|
||||
} catch (ParseException | SipException | InvalidArgumentException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@@ -817,7 +814,6 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
||||
}
|
||||
|
||||
public void inviteFromDeviceHandle(ServerTransaction serverTransaction, String requesterId, String channelId) throws InvalidArgumentException, ParseException, SipException, SdpException {
|
||||
|
||||
// 非上级平台请求,查询是否设备请求(通常为接收语音广播的设备)
|
||||
Device device = redisCatchStorage.getDevice(requesterId);
|
||||
AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(requesterId, channelId);
|
||||
@@ -918,125 +914,64 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
||||
sendRtpItem.setOnlyAudio(true);
|
||||
redisCatchStorage.updateSendRTPSever(sendRtpItem);
|
||||
|
||||
// hook监听等待设备推流上来
|
||||
// 添加订阅
|
||||
HookSubscribeForStreamChange subscribeKey = HookSubscribeFactory.on_stream_changed(app, stream, true, "rtsp", mediaServerItem.getId());
|
||||
|
||||
String finalSsrc = ssrc;
|
||||
// 流已经存在时直接推流
|
||||
// 设置等待推流的超时; 默认20s
|
||||
String waiteStreamTimeoutTaskKey = "waite-stream-" + device.getDeviceId() + audioBroadcastCatch.getChannelId();
|
||||
dynamicTask.startDelay(waiteStreamTimeoutTaskKey, ()->{
|
||||
logger.info("等待推流超时: {}/{}", app, stream);
|
||||
subscribe.removeSubscribe(subscribeKey);
|
||||
playService.stopAudioBroadcast(device.getDeviceId(), audioBroadcastCatch.getChannelId());
|
||||
// 发送bye
|
||||
try {
|
||||
responseAck(evt, Response.BUSY_HERE);
|
||||
} catch (SipException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (InvalidArgumentException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (ParseException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}, 20*1000);
|
||||
|
||||
boolean finalMediaTransmissionTCP = mediaTransmissionTCP;
|
||||
subscribe.addSubscribe(subscribeKey,
|
||||
(MediaServerItem mediaServerItemInUse, JSONObject json)->{
|
||||
logger.info("收到语音对讲推流");
|
||||
dynamicTask.stop(waiteStreamTimeoutTaskKey);
|
||||
MediaItem mediaItem = JSON.toJavaObject(json, MediaItem.class);
|
||||
Integer audioCodecId = null;
|
||||
if (mediaItem.getTracks() != null && mediaItem.getTracks().size() > 0) {
|
||||
for (int i = 0; i < mediaItem.getTracks().size(); i++) {
|
||||
MediaItem.MediaTrack mediaTrack = mediaItem.getTracks().get(i);
|
||||
if (mediaTrack.getCodecType() == 1) {
|
||||
audioCodecId = mediaTrack.getCodecId();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 (audioCodecId == null) {
|
||||
if (finalMediaTransmissionTCP) {
|
||||
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\r\n");
|
||||
}else {
|
||||
if (audioCodecId == 4) {
|
||||
if (finalMediaTransmissionTCP) {
|
||||
content.append("m=audio "+ sendRtpItem.getLocalPort()+" TCP/RTP/AVP 0\r\n");
|
||||
}else {
|
||||
content.append("m=audio "+ sendRtpItem.getLocalPort()+" RTP/AVP 0\r\n");
|
||||
}
|
||||
content.append("a=rtpmap:0 PCMU/8000\r\n");
|
||||
}else {
|
||||
if (finalMediaTransmissionTCP) {
|
||||
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\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="+ finalSsrc + "\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());
|
||||
|
||||
responseSdpAck(serverTransaction, content.toString(), parentPlatform);
|
||||
Dialog dialog = evt.getDialog();
|
||||
audioBroadcastCatch.setDialog((SIPDialog) dialog);
|
||||
audioBroadcastCatch.setRequest((SIPRequest) request);
|
||||
audioBroadcastManager.update(audioBroadcastCatch);
|
||||
} catch (SipException | InvalidArgumentException | ParseException | SdpParseException e) {
|
||||
logger.error("[命令发送失败] 语音对讲: {}", e.getMessage());
|
||||
}
|
||||
});
|
||||
// }
|
||||
String key = DeferredResultHolder.CALLBACK_CMD_BROADCAST + device.getDeviceId();
|
||||
WVPResult<AudioBroadcastResult> wvpResult = new WVPResult<>();
|
||||
wvpResult.setCode(0);
|
||||
wvpResult.setMsg("success");
|
||||
AudioBroadcastResult audioBroadcastResult = new AudioBroadcastResult();
|
||||
audioBroadcastResult.setApp(app);
|
||||
audioBroadcastResult.setStream(stream);
|
||||
audioBroadcastResult.setStreamInfo(mediaService.getStreamInfoByAppAndStream(mediaServerItem, app, stream, null, null, null,false));
|
||||
audioBroadcastResult.setCodec("G.711");
|
||||
wvpResult.setData(audioBroadcastResult);
|
||||
RequestMessage requestMessage = new RequestMessage();
|
||||
requestMessage.setKey(key);
|
||||
requestMessage.setData(wvpResult);
|
||||
resultHolder.invokeAllResult(requestMessage);
|
||||
Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, app, stream);
|
||||
if (streamReady) {
|
||||
sendOk(device, sendRtpItem, sdp, serverTransaction, mediaServerItem, mediaTransmissionTCP, ssrc);
|
||||
}else {
|
||||
logger.warn("[语音通话], 未发现待推送的流,app={},stream={}", app, stream);
|
||||
playService.stopAudioBroadcast(device.getDeviceId(), audioBroadcastCatch.getChannelId());
|
||||
}
|
||||
} else {
|
||||
logger.warn("来自无效设备/平台的请求");
|
||||
responseAck(serverTransaction, Response.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
void sendOk(Device device, SendRtpItem sendRtpItem, SessionDescription sdp, ServerTransaction serverTransaction, MediaServerItem mediaServerItem, boolean mediaTransmissionTCP, String ssrc){
|
||||
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 sipResponse = responseSdpAck(serverTransaction, content.toString(), parentPlatform);
|
||||
|
||||
AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(device.getDeviceId(), sendRtpItem.getChannelId());
|
||||
|
||||
audioBroadcastCatch.setStatus(AudioBroadcastCatchStatus.Ok);
|
||||
audioBroadcastCatch.setSipTransactionInfoByRequset(sipResponse);
|
||||
audioBroadcastManager.update(audioBroadcastCatch);
|
||||
} catch (SipException | InvalidArgumentException | ParseException | SdpParseException e) {
|
||||
logger.error("[命令发送失败] 语音对讲 回复200OK(SDP): {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,8 @@ import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.RequestEvent;
|
||||
import javax.sip.ServerTransaction;
|
||||
import javax.sip.SipException;
|
||||
import javax.sip.*;
|
||||
import javax.sip.header.*;
|
||||
import javax.sip.message.Request;
|
||||
import javax.sip.message.Response;
|
||||
@@ -116,10 +112,12 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
|
||||
|
||||
if (expiresHeader == null) {
|
||||
response = getMessageFactory().createResponse(Response.BAD_REQUEST, request);
|
||||
ServerTransaction serverTransaction = getServerTransaction(evt);
|
||||
serverTransaction.sendResponse(response);
|
||||
if (serverTransaction.getDialog() != null) {
|
||||
serverTransaction.getDialog().delete();
|
||||
if (evt.getDialog() != null ) {
|
||||
if (evt.getDialog().isServer()) {
|
||||
ServerTransaction serverTransaction = getServerTransaction(evt);
|
||||
serverTransaction.sendResponse(response);
|
||||
serverTransaction.getDialog().delete();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -176,19 +174,13 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
|
||||
} catch (SipException | InvalidArgumentException | NoSuchAlgorithmException | ParseException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void sendResponse(RequestEvent evt, Response response) throws InvalidArgumentException, SipException {
|
||||
ServerTransaction serverTransaction = getServerTransaction(evt);
|
||||
if (serverTransaction == null) {
|
||||
logger.warn("[回复失败]:{}", response);
|
||||
return;
|
||||
}
|
||||
serverTransaction.sendResponse(response);
|
||||
if (serverTransaction.getDialog() != null) {
|
||||
serverTransaction.getDialog().delete();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
|
||||
|
||||
import com.genersoft.iot.vmp.common.VideoManagerConstants;
|
||||
import com.genersoft.iot.vmp.conf.DynamicTask;
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.CmdType;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo;
|
||||
import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeHandlerTask;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
|
||||
import gov.nist.javax.sip.SipProviderImpl;
|
||||
import gov.nist.javax.sip.message.SIPRequest;
|
||||
|
||||
@@ -3,11 +3,15 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.AckRequestProcessor;
|
||||
import org.dom4j.Element;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.RequestEvent;
|
||||
import javax.sip.ServerTransaction;
|
||||
import javax.sip.SipException;
|
||||
import javax.sip.message.Response;
|
||||
import java.text.ParseException;
|
||||
@@ -18,6 +22,8 @@ import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText;
|
||||
|
||||
public abstract class MessageHandlerAbstract extends SIPRequestProcessorParent implements IMessageHandler{
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(MessageHandlerAbstract.class);
|
||||
|
||||
public Map<String, IMessageHandler> messageHandlerMap = new ConcurrentHashMap<>();
|
||||
|
||||
public void addHandler(String cmdType, IMessageHandler messageHandler) {
|
||||
@@ -48,14 +54,10 @@ public abstract class MessageHandlerAbstract extends SIPRequestProcessorParent i
|
||||
|
||||
public void handNullCmd(RequestEvent evt){
|
||||
try {
|
||||
responseAck(evt, Response.OK);
|
||||
} catch (SipException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (InvalidArgumentException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (ParseException e) {
|
||||
throw new RuntimeException(e);
|
||||
ServerTransaction serverTransaction = getServerTransaction(evt);
|
||||
responseAck(serverTransaction, Response.OK);
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
logger.error("[命令发送失败] 回复200 OK: {}", e.getMessage());
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,16 +52,17 @@ public class BroadcastResponseMessageHandler extends SIPRequestProcessorParent i
|
||||
public void handForDevice(RequestEvent evt, Device device, Element rootElement) {
|
||||
try {
|
||||
String channelId = getText(rootElement, "DeviceID");
|
||||
ServerTransaction serverTransaction = getServerTransaction(evt);
|
||||
if (!audioBroadcastManager.exit(device.getDeviceId(), channelId)) {
|
||||
// 回复410
|
||||
responseAck(evt, Response.GONE);
|
||||
responseAck(serverTransaction, Response.GONE);
|
||||
return;
|
||||
}
|
||||
logger.info("收到语音广播的回复:{}/{}", device.getDeviceId(), channelId );
|
||||
AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(device.getDeviceId(), channelId);
|
||||
audioBroadcastCatch.setStatus(AudioBroadcastCatchStatus.WaiteInvite);
|
||||
audioBroadcastManager.update(audioBroadcastCatch);
|
||||
responseAck(evt, Response.OK);
|
||||
responseAck(serverTransaction, Response.OK);
|
||||
} catch (ParseException | SipException | InvalidArgumentException e) {
|
||||
logger.error("[命令发送失败] 国标级联 语音喊话: {}", e.getMessage());
|
||||
}
|
||||
|
||||
@@ -113,4 +113,7 @@ public class SipUtils {
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static String getNewCallId() {
|
||||
return (int) Math.floor(Math.random() * 10000) + "";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user