Merge remote-tracking branch 'origin/master' into wvp-28181-2.0
# Conflicts: # README.md # src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java # src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorFactory.java # src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java # src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java # src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java # src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHTTPProxyController.java # src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java # src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java # src/main/java/com/genersoft/iot/vmp/storager/jdbc/VideoManagerJdbcStoragerImpl.java # src/main/java/com/genersoft/iot/vmp/storager/redis/VideoManagerRedisStoragerImpl.java # src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java # src/main/java/com/genersoft/iot/vmp/vmanager/playback/PlaybackController.java # src/main/java/com/genersoft/iot/vmp/vmanager/service/impl/PlayServiceImpl.java # src/main/java/com/genersoft/iot/vmp/web/ApiStreamController.java # src/main/resources/application-dev.yml # web_src/src/components/gb28181/devicePlayer.vue
This commit is contained in:
@@ -8,8 +8,10 @@ import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.sip.*;
|
||||
import javax.sip.header.CallIdHeader;
|
||||
import javax.sip.message.Response;
|
||||
|
||||
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -34,6 +36,9 @@ public class SipLayer implements SipListener {
|
||||
@Autowired
|
||||
private SIPProcessorFactory processorFactory;
|
||||
|
||||
@Autowired
|
||||
private SipSubscribe sipSubscribe;
|
||||
|
||||
private SipStack sipStack;
|
||||
|
||||
private SipFactory sipFactory;
|
||||
@@ -133,17 +138,34 @@ public class SipLayer implements SipListener {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (evt.getResponse() != null && sipSubscribe.getOkSubscribesSize() > 0 ) {
|
||||
CallIdHeader callIdHeader = (CallIdHeader)evt.getResponse().getHeader(CallIdHeader.NAME);
|
||||
if (callIdHeader != null) {
|
||||
SipSubscribe.Event subscribe = sipSubscribe.getOkSubscribe(callIdHeader.getCallId());
|
||||
if (subscribe != null) {
|
||||
subscribe.response(evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
// } else if (status == Response.TRYING) {
|
||||
// trying不会回复
|
||||
} else if ((status >= 100) && (status < 200)) {
|
||||
// 增加其它无需回复的响应,如101、180等
|
||||
} else {
|
||||
logger.warn("接收到失败的response响应!status:" + status + ",message:" + response.getReasonPhrase()/* .getContent().toString()*/);
|
||||
if (evt.getResponse() != null && sipSubscribe.getErrorSubscribesSize() > 0 ) {
|
||||
CallIdHeader callIdHeader = (CallIdHeader)evt.getResponse().getHeader(CallIdHeader.NAME);
|
||||
if (callIdHeader != null) {
|
||||
SipSubscribe.Event subscribe = sipSubscribe.getErrorSubscribe(callIdHeader.getCallId());
|
||||
if (subscribe != null) {
|
||||
subscribe.response(evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// trying不会回复
|
||||
// if (status == Response.TRYING) {
|
||||
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,6 +27,7 @@ package com.genersoft.iot.vmp.gb28181.auth;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Random;
|
||||
|
||||
@@ -103,9 +104,12 @@ public class DigestServerAuthenticationHelper {
|
||||
.createWWWAuthenticateHeader(DEFAULT_SCHEME);
|
||||
proxyAuthenticate.setParameter("realm", realm);
|
||||
proxyAuthenticate.setParameter("nonce", generateNonce());
|
||||
|
||||
proxyAuthenticate.setParameter("opaque", "");
|
||||
proxyAuthenticate.setParameter("stale", "FALSE");
|
||||
proxyAuthenticate.setParameter("algorithm", DEFAULT_ALGORITHM);
|
||||
|
||||
// proxyAuthenticate.setParameter("qop", "auth");
|
||||
response.setHeader(proxyAuthenticate);
|
||||
} catch (Exception ex) {
|
||||
InternalErrorHandler.handleException(ex);
|
||||
@@ -170,42 +174,116 @@ public class DigestServerAuthenticationHelper {
|
||||
public boolean doAuthenticatePlainTextPassword(Request request, String pass) {
|
||||
AuthorizationHeader authHeader = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
|
||||
if ( authHeader == null ) return false;
|
||||
String realm = authHeader.getRealm();
|
||||
String username = authHeader.getUsername();
|
||||
|
||||
|
||||
String realm = authHeader.getRealm().trim();
|
||||
String username = authHeader.getUsername().trim();
|
||||
|
||||
if ( username == null || realm == null ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
String nonce = authHeader.getNonce();
|
||||
URI uri = authHeader.getURI();
|
||||
if (uri == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// qop 保护质量 包含auth(默认的)和auth-int(增加了报文完整性检测)两种策略
|
||||
String qop = authHeader.getQop();
|
||||
|
||||
// 客户端随机数,这是一个不透明的字符串值,由客户端提供,并且客户端和服务器都会使用,以避免用明文文本。
|
||||
// 这使得双方都可以查验对方的身份,并对消息的完整性提供一些保护
|
||||
String cNonce = authHeader.getCNonce();
|
||||
|
||||
// nonce计数器,是一个16进制的数值,表示同一nonce下客户端发送出请求的数量
|
||||
int nc = authHeader.getNonceCount();
|
||||
String ncStr = new DecimalFormat("00000000").format(nc);
|
||||
// String ncStr = new DecimalFormat("00000000").format(Integer.parseInt(nc + "", 16));
|
||||
|
||||
String A1 = username + ":" + realm + ":" + pass;
|
||||
String A2 = request.getMethod().toUpperCase() + ":" + uri.toString();
|
||||
byte mdbytes[] = messageDigest.digest(A1.getBytes());
|
||||
String HA1 = toHexString(mdbytes);
|
||||
System.out.println("A1: " + A1);
|
||||
System.out.println("A2: " + A2);
|
||||
|
||||
|
||||
mdbytes = messageDigest.digest(A2.getBytes());
|
||||
String HA2 = toHexString(mdbytes);
|
||||
|
||||
System.out.println("HA1: " + HA1);
|
||||
System.out.println("HA2: " + HA2);
|
||||
String cnonce = authHeader.getCNonce();
|
||||
System.out.println("nonce: " + nonce);
|
||||
System.out.println("nc: " + ncStr);
|
||||
System.out.println("cnonce: " + cnonce);
|
||||
System.out.println("qop: " + qop);
|
||||
String KD = HA1 + ":" + nonce;
|
||||
if (cnonce != null) {
|
||||
KD += ":" + cnonce;
|
||||
|
||||
if (qop != null && qop.equals("auth") ) {
|
||||
if (nc != -1) {
|
||||
KD += ":" + ncStr;
|
||||
}
|
||||
if (cnonce != null) {
|
||||
KD += ":" + cnonce;
|
||||
}
|
||||
KD += ":" + qop;
|
||||
}
|
||||
KD += ":" + HA2;
|
||||
System.out.println("KD: " + KD);
|
||||
mdbytes = messageDigest.digest(KD.getBytes());
|
||||
String mdString = toHexString(mdbytes);
|
||||
System.out.println("mdString: " + mdString);
|
||||
String response = authHeader.getResponse();
|
||||
System.out.println("response: " + response);
|
||||
return mdString.equals(response);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) throws NoSuchAlgorithmException {
|
||||
MessageDigest messageDigest2 = MessageDigest.getInstance(DEFAULT_ALGORITHM);
|
||||
String realm = "DS-2CD2520F";
|
||||
String username = "admin";
|
||||
String passwd = "12345";
|
||||
|
||||
String nonce = "4d6a553452444d30525441364e6d4d304e6a68684e47553d";
|
||||
|
||||
String uri = "/ISAPI/Streaming/channels/101/picture";
|
||||
// qop 保护质量 包含auth(默认的)和auth-int(增加了报文完整性检测)两种策略
|
||||
String qop = "auth";
|
||||
|
||||
// 客户端随机数,这是一个不透明的字符串值,由客户端提供,并且客户端和服务器都会使用,以避免用明文文本。
|
||||
// 这使得双方都可以查验对方的身份,并对消息的完整性提供一些保护
|
||||
String cNonce = "C1A5298F939E87E8F962A5EDFC206918";
|
||||
|
||||
// nonce计数器,是一个16进制的数值,表示同一nonce下客户端发送出请求的数量
|
||||
int nc = 1;
|
||||
|
||||
String A1 = username + ":" + realm + ":" + passwd;
|
||||
System.out.println("A1: " + A1);
|
||||
String A2 = "GET" + ":" + uri.toString();
|
||||
System.out.println("A2: " + A2);
|
||||
byte mdbytes[] = messageDigest2.digest(A1.getBytes());
|
||||
String HA1 = toHexString(mdbytes);
|
||||
System.out.println("HA1: " + HA1);
|
||||
|
||||
mdbytes = messageDigest2.digest(A2.getBytes());
|
||||
String HA2 = toHexString(mdbytes);
|
||||
System.out.println("HA2: " + HA2);
|
||||
String cnonce = "93d4d37df32e1a85";
|
||||
String KD = HA1 + ":" + nonce;
|
||||
|
||||
if (nc != -1) {
|
||||
KD += ":" + "00000001";
|
||||
}
|
||||
if (cnonce != null) {
|
||||
KD += ":" + cnonce;
|
||||
}
|
||||
if (qop != null) {
|
||||
KD += ":" + qop;
|
||||
}
|
||||
KD += ":" + HA2;
|
||||
System.out.println("KD: " + KD);
|
||||
mdbytes = messageDigest2.digest(KD.getBytes());
|
||||
String mdString = toHexString(mdbytes);
|
||||
String response = "3993a815e5cdaf4470e9b4f9bd41cf4a";
|
||||
System.out.println(mdString);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,6 @@ public class RegisterLogicHandler {
|
||||
// TODO 后续处理,只有第一次注册时调用查询设备信息,如需更新调用更新API接口
|
||||
cmder.deviceInfoQuery(device);
|
||||
|
||||
cmder.catalogQuery(device);
|
||||
cmder.catalogQuery(device, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package com.genersoft.iot.vmp.gb28181.bean;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class Device {
|
||||
|
||||
/**
|
||||
@@ -45,25 +42,37 @@ public class Device {
|
||||
*/
|
||||
private String streamMode;
|
||||
|
||||
/**
|
||||
* wan地址_ip
|
||||
*/
|
||||
private String ip;
|
||||
|
||||
/**
|
||||
* wan地址_port
|
||||
*/
|
||||
private int port;
|
||||
|
||||
/**
|
||||
* wan地址
|
||||
*/
|
||||
private Host host;
|
||||
private String hostAddress;
|
||||
|
||||
/**
|
||||
* 在线
|
||||
*/
|
||||
private int online;
|
||||
|
||||
|
||||
/**
|
||||
* 通道列表
|
||||
* 注册时间
|
||||
*/
|
||||
// private Map<String,DeviceChannel> channelMap;
|
||||
private Long registerTimeMillis;
|
||||
|
||||
/**
|
||||
* 通道个数
|
||||
*/
|
||||
private int channelCount;
|
||||
|
||||
private List<String> channelList;
|
||||
|
||||
public String getDeviceId() {
|
||||
return deviceId;
|
||||
}
|
||||
@@ -120,12 +129,28 @@ public class Device {
|
||||
this.streamMode = streamMode;
|
||||
}
|
||||
|
||||
public Host getHost() {
|
||||
return host;
|
||||
public String getIp() {
|
||||
return ip;
|
||||
}
|
||||
|
||||
public void setHost(Host host) {
|
||||
this.host = host;
|
||||
public void setIp(String ip) {
|
||||
this.ip = ip;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public String getHostAddress() {
|
||||
return hostAddress;
|
||||
}
|
||||
|
||||
public void setHostAddress(String hostAddress) {
|
||||
this.hostAddress = hostAddress;
|
||||
}
|
||||
|
||||
public int getOnline() {
|
||||
@@ -144,11 +169,11 @@ public class Device {
|
||||
this.channelCount = channelCount;
|
||||
}
|
||||
|
||||
public List<String> getChannelList() {
|
||||
return channelList;
|
||||
public Long getRegisterTimeMillis() {
|
||||
return registerTimeMillis;
|
||||
}
|
||||
|
||||
public void setChannelList(List<String> channelList) {
|
||||
this.channelList = channelList;
|
||||
public void setRegisterTimeMillis(Long registerTimeMillis) {
|
||||
this.registerTimeMillis = registerTimeMillis;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,17 @@ package com.genersoft.iot.vmp.gb28181.bean;
|
||||
|
||||
public class DeviceChannel {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 通道id
|
||||
*/
|
||||
private String channelId;
|
||||
|
||||
/**
|
||||
* 设备id
|
||||
*/
|
||||
private String deviceId;
|
||||
|
||||
/**
|
||||
* 通道名
|
||||
@@ -141,18 +148,20 @@ public class DeviceChannel {
|
||||
/**
|
||||
* 流唯一编号,存在表示正在直播
|
||||
*/
|
||||
private String ssrc;
|
||||
private String streamId;
|
||||
|
||||
/**
|
||||
* 是否含有音频
|
||||
*/
|
||||
private boolean hasAudio;
|
||||
private boolean hasAudio;
|
||||
|
||||
/**
|
||||
* 是否正在播放
|
||||
*/
|
||||
private boolean play;
|
||||
public String getDeviceId() {
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
public void setDeviceId(String deviceId) {
|
||||
this.deviceId = deviceId;
|
||||
}
|
||||
|
||||
public void setPTZType(int PTZType) {
|
||||
this.PTZType = PTZType;
|
||||
@@ -379,14 +388,6 @@ public class DeviceChannel {
|
||||
this.subCount = subCount;
|
||||
}
|
||||
|
||||
public String getSsrc() {
|
||||
return ssrc;
|
||||
}
|
||||
|
||||
public void setSsrc(String ssrc) {
|
||||
this.ssrc = ssrc;
|
||||
}
|
||||
|
||||
public boolean isHasAudio() {
|
||||
return hasAudio;
|
||||
}
|
||||
@@ -395,11 +396,11 @@ public class DeviceChannel {
|
||||
this.hasAudio = hasAudio;
|
||||
}
|
||||
|
||||
public boolean isPlay() {
|
||||
return play;
|
||||
public String getStreamId() {
|
||||
return streamId;
|
||||
}
|
||||
|
||||
public void setPlay(boolean play) {
|
||||
this.play = play;
|
||||
public void setStreamId(String streamId) {
|
||||
this.streamId = streamId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.genersoft.iot.vmp.gb28181.event;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.sip.ResponseEvent;
|
||||
import javax.sip.message.Request;
|
||||
import java.util.EventObject;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Component
|
||||
public class SipSubscribe {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(SipSubscribe.class);
|
||||
|
||||
private Map<String, SipSubscribe.Event> errorSubscribes = new ConcurrentHashMap<>();
|
||||
|
||||
private Map<String, SipSubscribe.Event> okSubscribes = new ConcurrentHashMap<>();
|
||||
|
||||
public interface Event {
|
||||
void response(ResponseEvent event);
|
||||
}
|
||||
|
||||
public void addErrorSubscribe(String key, SipSubscribe.Event event) {
|
||||
errorSubscribes.put(key, event);
|
||||
}
|
||||
|
||||
public void addOkSubscribe(String key, SipSubscribe.Event event) {
|
||||
okSubscribes.put(key, event);
|
||||
}
|
||||
|
||||
public SipSubscribe.Event getErrorSubscribe(String key) {
|
||||
return errorSubscribes.get(key);
|
||||
}
|
||||
|
||||
public SipSubscribe.Event getOkSubscribe(String key) {
|
||||
return okSubscribes.get(key);
|
||||
}
|
||||
|
||||
public int getErrorSubscribesSize(){
|
||||
return errorSubscribes.size();
|
||||
}
|
||||
public int getOkSubscribesSize(){
|
||||
return okSubscribes.size();
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,10 @@ import javax.sip.header.CSeqHeader;
|
||||
import javax.sip.message.Request;
|
||||
import javax.sip.message.Response;
|
||||
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.response.impl.*;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.response.impl.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -54,7 +56,10 @@ public class SIPProcessorFactory {
|
||||
|
||||
@Autowired
|
||||
private IVideoManagerStorager storager;
|
||||
|
||||
|
||||
@Autowired
|
||||
private IRedisCatchStorage redisCatchStorage;
|
||||
|
||||
@Autowired
|
||||
private EventPublisher publisher;
|
||||
|
||||
@@ -82,10 +87,11 @@ public class SIPProcessorFactory {
|
||||
@Autowired
|
||||
@Lazy
|
||||
private RegisterResponseProcessor registerResponseProcessor;
|
||||
|
||||
|
||||
@Autowired
|
||||
private OtherResponseProcessor otherResponseProcessor;
|
||||
|
||||
|
||||
|
||||
// 注:这里使用注解会导致循环依赖注入,暂用springBean
|
||||
private SipProvider tcpSipProvider;
|
||||
|
||||
@@ -140,6 +146,7 @@ public class SIPProcessorFactory {
|
||||
processor.setOffLineDetector(offLineDetector);
|
||||
processor.setCmder(cmder);
|
||||
processor.setStorager(storager);
|
||||
processor.setRedisCatchStorage(redisCatchStorage);
|
||||
return processor;
|
||||
} else {
|
||||
return new OtherRequestProcessor();
|
||||
@@ -147,6 +154,7 @@ public class SIPProcessorFactory {
|
||||
}
|
||||
|
||||
public ISIPResponseProcessor createResponseProcessor(ResponseEvent evt) {
|
||||
|
||||
Response response = evt.getResponse();
|
||||
CSeqHeader cseqHeader = (CSeqHeader) response.getHeader(CSeqHeader.NAME);
|
||||
String method = cseqHeader.getMethod();
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.callback;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -24,8 +25,10 @@ public class DeferredResultHolder {
|
||||
|
||||
public static final String CALLBACK_CMD_PlAY = "CALLBACK_PLAY";
|
||||
|
||||
private Map<String, DeferredResult> map = new HashMap<String, DeferredResult>();
|
||||
|
||||
public static final String CALLBACK_CMD_STOP = "CALLBACK_STOP";
|
||||
|
||||
private Map<String, DeferredResult> map = new ConcurrentHashMap<String, DeferredResult>();
|
||||
|
||||
public void put(String key, DeferredResult result) {
|
||||
map.put(key, result);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd;
|
||||
|
||||
import com.genersoft.iot.vmp.common.StreamInfo;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
||||
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
|
||||
|
||||
/**
|
||||
* @Description:设备能力接口,用于定义设备的控制、查询能力
|
||||
@@ -84,7 +84,7 @@ public interface ISIPCommander {
|
||||
* @param device 视频设备
|
||||
* @param channelId 预览通道
|
||||
*/
|
||||
void playStreamCmd(Device device, String channelId, ZLMHttpHookSubscribe.Event event);
|
||||
void playStreamCmd(Device device, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
|
||||
|
||||
/**
|
||||
* 请求回放视频流
|
||||
@@ -94,15 +94,16 @@ public interface ISIPCommander {
|
||||
* @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
|
||||
* @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
|
||||
*/
|
||||
void playbackStreamCmd(Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event);
|
||||
void playbackStreamCmd(Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
|
||||
|
||||
/**
|
||||
* 视频流停止
|
||||
*
|
||||
* @param ssrc ssrc
|
||||
*/
|
||||
void streamByeCmd(String ssrc, SipSubscribe.Event okEvent);
|
||||
void streamByeCmd(String ssrc);
|
||||
|
||||
|
||||
/**
|
||||
* 语音广播
|
||||
*
|
||||
@@ -176,7 +177,7 @@ public interface ISIPCommander {
|
||||
*
|
||||
* @param device 视频设备
|
||||
*/
|
||||
boolean catalogQuery(Device device);
|
||||
boolean catalogQuery(Device device, SipSubscribe.Event errorEvent);
|
||||
|
||||
/**
|
||||
* 查询录像信息
|
||||
@@ -214,4 +215,11 @@ public interface ISIPCommander {
|
||||
* @param device 视频设备
|
||||
*/
|
||||
boolean mobilePostitionQuery(Device device);
|
||||
|
||||
/**
|
||||
* 释放rtpserver
|
||||
* @param device
|
||||
* @param channelId
|
||||
*/
|
||||
void closeRTPServer(Device device, String channelId);
|
||||
}
|
||||
|
||||
@@ -47,9 +47,8 @@ public class SIPRequestHeaderProvider {
|
||||
|
||||
public Request createMessageRequest(Device device, String content, String viaTag, String fromTag, String toTag) throws ParseException, InvalidArgumentException, PeerUnavailableException {
|
||||
Request request = null;
|
||||
Host host = device.getHost();
|
||||
// sipuri
|
||||
SipURI requestURI = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), host.getAddress());
|
||||
SipURI requestURI = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
|
||||
// via
|
||||
ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
|
||||
ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(sipConfig.getSipIp(), sipConfig.getSipPort(),
|
||||
@@ -75,22 +74,21 @@ public class SIPRequestHeaderProvider {
|
||||
|
||||
request = sipFactory.createMessageFactory().createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader,
|
||||
toHeader, viaHeaders, maxForwards);
|
||||
ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
|
||||
ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("APPLICATION", "MANSCDP+xml");
|
||||
request.setContent(content, contentTypeHeader);
|
||||
return request;
|
||||
}
|
||||
|
||||
public Request createInviteRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag, String ssrc) throws ParseException, InvalidArgumentException, PeerUnavailableException {
|
||||
Request request = null;
|
||||
Host host = device.getHost();
|
||||
//请求行
|
||||
SipURI requestLine = sipFactory.createAddressFactory().createSipURI(channelId, host.getAddress());
|
||||
SipURI requestLine = sipFactory.createAddressFactory().createSipURI(channelId, device.getHostAddress());
|
||||
//via
|
||||
ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
|
||||
// ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(sipConfig.getSipIp(), sipConfig.getSipPort(), device.getTransport(), viaTag);
|
||||
ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(device.getHost().getIp(), device.getHost().getPort(), device.getTransport(), viaTag);
|
||||
ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(device.getIp(), device.getPort(), device.getTransport(), viaTag);
|
||||
viaHeader.setRPort();
|
||||
viaHeaders.add(viaHeader);
|
||||
|
||||
//from
|
||||
SipURI fromSipURI = sipFactory.createAddressFactory().createSipURI(sipConfig.getSipId(),sipConfig.getSipDomain());
|
||||
Address fromAddress = sipFactory.createAddressFactory().createAddress(fromSipURI);
|
||||
@@ -122,20 +120,18 @@ public class SIPRequestHeaderProvider {
|
||||
// Subject
|
||||
SubjectHeader subjectHeader = sipFactory.createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getSipId(), 0));
|
||||
request.addHeader(subjectHeader);
|
||||
ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "SDP");
|
||||
ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
|
||||
request.setContent(content, contentTypeHeader);
|
||||
return request;
|
||||
}
|
||||
|
||||
public Request createPlaybackInviteRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag) throws ParseException, InvalidArgumentException, PeerUnavailableException {
|
||||
Request request = null;
|
||||
Host host = device.getHost();
|
||||
//请求行
|
||||
SipURI requestLine = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), host.getAddress());
|
||||
//via
|
||||
SipURI requestLine = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
|
||||
// via
|
||||
ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
|
||||
// ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(sipConfig.getSipIp(), sipConfig.getSipPort(), device.getTransport(), viaTag);
|
||||
ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(device.getHost().getIp(), device.getHost().getPort(), device.getTransport(), viaTag);
|
||||
ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(device.getIp(), device.getPort(), device.getTransport(), viaTag);
|
||||
viaHeader.setRPort();
|
||||
viaHeaders.add(viaHeader);
|
||||
//from
|
||||
@@ -167,7 +163,7 @@ public class SIPRequestHeaderProvider {
|
||||
// Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory().createSipURI(sipConfig.getSipId(), device.getHost().getIp()+":"+device.getHost().getPort()));
|
||||
request.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
|
||||
|
||||
ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "SDP");
|
||||
ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
|
||||
request.setContent(content, contentTypeHeader);
|
||||
return request;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.sip.ClientTransaction;
|
||||
import javax.sip.Dialog;
|
||||
import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.SipException;
|
||||
import javax.sip.SipFactory;
|
||||
import javax.sip.SipProvider;
|
||||
import javax.sip.TransactionDoesNotExistException;
|
||||
import javax.sip.*;
|
||||
import javax.sip.address.SipURI;
|
||||
import javax.sip.header.CallIdHeader;
|
||||
import javax.sip.header.Header;
|
||||
import javax.sip.header.ViaHeader;
|
||||
import javax.sip.message.Request;
|
||||
|
||||
@@ -19,9 +16,13 @@ import com.alibaba.fastjson.JSONObject;
|
||||
import com.genersoft.iot.vmp.common.StreamInfo;
|
||||
import com.genersoft.iot.vmp.conf.MediaServerConfig;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
|
||||
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZLMUtils;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
|
||||
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.beans.factory.annotation.Value;
|
||||
@@ -41,6 +42,8 @@ import com.genersoft.iot.vmp.gb28181.utils.DateUtil;
|
||||
*/
|
||||
@Component
|
||||
public class SIPCommander implements ISIPCommander {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SIPCommander.class);
|
||||
|
||||
@Autowired
|
||||
private SipConfig sipConfig;
|
||||
@@ -53,6 +56,9 @@ public class SIPCommander implements ISIPCommander {
|
||||
|
||||
@Autowired
|
||||
private IVideoManagerStorager storager;
|
||||
|
||||
@Autowired
|
||||
private IRedisCatchStorage redisCatchStorage;
|
||||
|
||||
@Autowired
|
||||
@Qualifier(value="tcpSipProvider")
|
||||
@@ -63,14 +69,20 @@ public class SIPCommander implements ISIPCommander {
|
||||
private SipProvider udpSipProvider;
|
||||
|
||||
@Autowired
|
||||
private ZLMUtils zlmUtils;
|
||||
private ZLMRTPServerFactory zlmrtpServerFactory;
|
||||
|
||||
@Value("${media.rtp.enable}")
|
||||
private boolean rtpEnable;
|
||||
|
||||
@Value("${media.seniorSdp}")
|
||||
private boolean seniorSdp;
|
||||
|
||||
@Autowired
|
||||
private ZLMHttpHookSubscribe subscribe;
|
||||
|
||||
@Autowired
|
||||
private SipSubscribe sipSubscribe;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
@@ -176,19 +188,29 @@ public class SIPCommander implements ISIPCommander {
|
||||
* @param moveSpeed 镜头移动速度 默认 0XFF (0-255)
|
||||
* @param zoomSpeed 镜头缩放速度 默认 0X1 (0-255)
|
||||
*/
|
||||
public static String frontEndCmdString(int cmdCode, int parameter1, int parameter2, int combineCode2) {
|
||||
|
||||
/**
|
||||
* 云台指令码计算
|
||||
*
|
||||
* @param cmdCode 指令码
|
||||
* @param horizonSpeed 水平移动速度
|
||||
* @param verticalSpeed 垂直移动速度
|
||||
* @param zoomSpeed 缩放速度
|
||||
* @return
|
||||
*/
|
||||
public static String frontEndCmdString(int cmdCode, int horizonSpeed, int verticalSpeed, int zoomSpeed) {
|
||||
StringBuilder builder = new StringBuilder("A50F01");
|
||||
String strTmp;
|
||||
strTmp = String.format("%02X", cmdCode);
|
||||
builder.append(strTmp, 0, 2);
|
||||
strTmp = String.format("%02X", parameter1);
|
||||
strTmp = String.format("%02X", horizonSpeed);
|
||||
builder.append(strTmp, 0, 2);
|
||||
strTmp = String.format("%02X", parameter2);
|
||||
strTmp = String.format("%02X", verticalSpeed);
|
||||
builder.append(strTmp, 0, 2);
|
||||
strTmp = String.format("%X", combineCode2);
|
||||
strTmp = String.format("%X", zoomSpeed);
|
||||
builder.append(strTmp, 0, 1).append("0");
|
||||
//计算校验码
|
||||
int checkCode = (0XA5 + 0X0F + 0X01 + cmdCode + parameter1 + parameter2 + (combineCode2 & 0XF0)) % 0X100;
|
||||
int checkCode = (0XA5 + 0X0F + 0X01 + cmdCode + horizonSpeed + verticalSpeed + (zoomSpeed & 0XF0)) % 0X100;
|
||||
strTmp = String.format("%02X", checkCode);
|
||||
builder.append(strTmp, 0, 2);
|
||||
return builder.toString();
|
||||
@@ -237,14 +259,14 @@ public class SIPCommander implements ISIPCommander {
|
||||
* @param device 控制设备
|
||||
* @param channelId 预览通道
|
||||
* @param cmdCode 指令码
|
||||
* @param parameter1 数据1
|
||||
* @param parameter2 数据2
|
||||
* @param combineCode2 组合码2
|
||||
* @param horizonSpeed 水平移动速度
|
||||
* @param verticalSpeed 垂直移动速度
|
||||
* @param zoomSpeed 缩放速度
|
||||
*/
|
||||
@Override
|
||||
public boolean frontEndCmd(Device device, String channelId, int cmdCode, int parameter1, int parameter2, int combineCode2) {
|
||||
public boolean frontEndCmd(Device device, String channelId, int cmdCode, int horizonSpeed, int verticalSpeed, int zoomSpeed) {
|
||||
try {
|
||||
String cmdStr= frontEndCmdString(cmdCode, parameter1, parameter2, combineCode2);
|
||||
String cmdStr= frontEndCmdString(cmdCode, horizonSpeed, verticalSpeed, zoomSpeed);
|
||||
System.out.println("控制字符串:" + cmdStr);
|
||||
StringBuffer ptzXml = new StringBuffer(200);
|
||||
ptzXml.append("<?xml version=\"1.0\" ?>\r\n");
|
||||
@@ -258,7 +280,6 @@ public class SIPCommander implements ISIPCommander {
|
||||
ptzXml.append("</Control>\r\n");
|
||||
|
||||
Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), "ViaPtzBranch", "FromPtzTag", "ToPtzTag");
|
||||
|
||||
transmitRequest(device, request);
|
||||
return true;
|
||||
} catch (SipException | ParseException | InvalidArgumentException e) {
|
||||
@@ -266,28 +287,39 @@ public class SIPCommander implements ISIPCommander {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求预览视频流
|
||||
*
|
||||
* 请求预览视频流
|
||||
* @param device 视频设备
|
||||
* @param channelId 预览通道
|
||||
* @param event hook订阅
|
||||
* @param errorEvent sip错误订阅
|
||||
*/
|
||||
@Override
|
||||
public void playStreamCmd(Device device, String channelId, ZLMHttpHookSubscribe.Event event) {
|
||||
public void playStreamCmd(Device device, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent) {
|
||||
try {
|
||||
|
||||
String ssrc = streamSession.createPlaySsrc();
|
||||
String streamId = null;
|
||||
if (rtpEnable) {
|
||||
streamId = String.format("gb_play_%s_%s", device.getDeviceId(), channelId);
|
||||
}else {
|
||||
streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
|
||||
}
|
||||
String streamMode = device.getStreamMode().toUpperCase();
|
||||
MediaServerConfig mediaInfo = storager.getMediaInfo();
|
||||
MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
|
||||
if (mediaInfo == null) {
|
||||
logger.warn("点播时发现ZLM尚未连接...");
|
||||
return;
|
||||
}
|
||||
String mediaPort = null;
|
||||
// 使用动态udp端口
|
||||
if (rtpEnable) {
|
||||
mediaPort = zlmUtils.getNewRTPPort(ssrc) + "";
|
||||
mediaPort = zlmrtpServerFactory.createRTPServer(streamId) + "";
|
||||
}else {
|
||||
mediaPort = mediaInfo.getRtpProxyPort();
|
||||
}
|
||||
|
||||
String streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
|
||||
// 添加订阅
|
||||
JSONObject subscribeKey = new JSONObject();
|
||||
subscribeKey.put("app", "rtp");
|
||||
@@ -297,7 +329,8 @@ public class SIPCommander implements ISIPCommander {
|
||||
//
|
||||
StringBuffer content = new StringBuffer(200);
|
||||
content.append("v=0\r\n");
|
||||
content.append("o="+channelId+" 0 0 IN IP4 "+mediaInfo.getWanIp()+"\r\n");
|
||||
// content.append("o="+channelId+" 0 0 IN IP4 "+mediaInfo.getWanIp()+"\r\n");
|
||||
content.append("o="+"00000"+" 0 0 IN IP4 "+mediaInfo.getWanIp()+"\r\n");
|
||||
content.append("s=Play\r\n");
|
||||
content.append("c=IN IP4 "+mediaInfo.getWanIp()+"\r\n");
|
||||
content.append("t=0 0\r\n");
|
||||
@@ -327,17 +360,14 @@ public class SIPCommander implements ISIPCommander {
|
||||
}
|
||||
content.append("y="+ssrc+"\r\n");//ssrc
|
||||
|
||||
// String fromTag = UUID.randomUUID().toString();
|
||||
// Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), null, fromTag, null, ssrc);
|
||||
|
||||
Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), null, "live", null, ssrc);
|
||||
|
||||
ClientTransaction transaction = transmitRequest(device, request);
|
||||
streamSession.put(ssrc, transaction);
|
||||
DeviceChannel deviceChannel = storager.queryChannel(device.getDeviceId(), channelId);
|
||||
if (deviceChannel != null) {
|
||||
deviceChannel.setSsrc(ssrc);
|
||||
storager.updateChannel(device.getDeviceId(), deviceChannel);
|
||||
}
|
||||
ClientTransaction transaction = transmitRequest(device, request, errorEvent);
|
||||
streamSession.put(streamId, transaction);
|
||||
|
||||
// TODO 订阅SIP response,处理对方的错误返回
|
||||
|
||||
|
||||
} catch ( SipException | ParseException | InvalidArgumentException e) {
|
||||
@@ -354,9 +384,10 @@ public class SIPCommander implements ISIPCommander {
|
||||
* @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
|
||||
*/
|
||||
@Override
|
||||
public void playbackStreamCmd(Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event) {
|
||||
public void playbackStreamCmd(Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event
|
||||
, SipSubscribe.Event errorEvent) {
|
||||
try {
|
||||
MediaServerConfig mediaInfo = storager.getMediaInfo();
|
||||
MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
|
||||
String ssrc = streamSession.createPlayBackSsrc();
|
||||
String streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
|
||||
// 添加订阅
|
||||
@@ -378,57 +409,91 @@ public class SIPCommander implements ISIPCommander {
|
||||
String mediaPort = null;
|
||||
// 使用动态udp端口
|
||||
if (rtpEnable) {
|
||||
mediaPort = zlmUtils.getNewRTPPort(ssrc) + "";
|
||||
mediaPort = zlmrtpServerFactory.createRTPServer(streamId) + "";
|
||||
}else {
|
||||
mediaPort = mediaInfo.getRtpProxyPort();
|
||||
}
|
||||
String streamMode = device.getStreamMode().toUpperCase();
|
||||
if("TCP-PASSIVE".equals(streamMode)) {
|
||||
content.append("m=video "+ mediaPort +" TCP/RTP/AVP 126 125 99 34 98 97 96\r\n");
|
||||
}else if ("TCP-ACTIVE".equals(streamMode)) {
|
||||
content.append("m=video "+ mediaPort +" TCP/RTP/AVP 126 125 99 34 98 97 96\r\n");
|
||||
}else if("UDP".equals(streamMode)) {
|
||||
content.append("m=video "+ mediaPort +" RTP/AVP 126 125 99 34 98 97 96\r\n");
|
||||
}
|
||||
content.append("a=recvonly\r\n");
|
||||
content.append("a=fmtp:126 profile-level-id=42e01e\r\n");
|
||||
content.append("a=rtpmap:126 H264/90000\r\n");
|
||||
content.append("a=rtpmap:125 H264S/90000\r\n");
|
||||
content.append("a=fmtp:125 profile-level-id=42e01e\r\n");
|
||||
content.append("a=rtpmap:99 MP4V-ES/90000\r\n");
|
||||
content.append("a=fmtp:99 profile-level-id=3\r\n");
|
||||
content.append("a=rtpmap:98 H264/90000\r\n");
|
||||
content.append("a=rtpmap:97 MPEG4/90000\r\n");
|
||||
content.append("a=rtpmap:96 PS/90000\r\n");
|
||||
if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式
|
||||
content.append("a=setup:passive\r\n");
|
||||
content.append("a=connection:new\r\n");
|
||||
}else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式
|
||||
content.append("a=setup:active\r\n");
|
||||
content.append("a=connection:new\r\n");
|
||||
|
||||
if (seniorSdp) {
|
||||
if("TCP-PASSIVE".equals(streamMode)) {
|
||||
content.append("m=video "+ mediaPort +" TCP/RTP/AVP 126 125 99 34 98 97 96\r\n");
|
||||
}else if ("TCP-ACTIVE".equals(streamMode)) {
|
||||
content.append("m=video "+ mediaPort +" TCP/RTP/AVP 126 125 99 34 98 97 96\r\n");
|
||||
}else if("UDP".equals(streamMode)) {
|
||||
content.append("m=video "+ mediaPort +" RTP/AVP 126 125 99 34 98 97 96\r\n");
|
||||
}
|
||||
content.append("a=recvonly\r\n");
|
||||
content.append("a=fmtp:126 profile-level-id=42e01e\r\n");
|
||||
content.append("a=rtpmap:126 H264/90000\r\n");
|
||||
content.append("a=rtpmap:125 H264S/90000\r\n");
|
||||
content.append("a=fmtp:125 profile-level-id=42e01e\r\n");
|
||||
content.append("a=rtpmap:99 MP4V-ES/90000\r\n");
|
||||
content.append("a=fmtp:99 profile-level-id=3\r\n");
|
||||
content.append("a=rtpmap:98 H264/90000\r\n");
|
||||
content.append("a=rtpmap:97 MPEG4/90000\r\n");
|
||||
content.append("a=rtpmap:96 PS/90000\r\n");
|
||||
if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式
|
||||
content.append("a=setup:passive\r\n");
|
||||
content.append("a=connection:new\r\n");
|
||||
}else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式
|
||||
content.append("a=setup:active\r\n");
|
||||
content.append("a=connection:new\r\n");
|
||||
}
|
||||
}else {
|
||||
if("TCP-PASSIVE".equals(streamMode)) {
|
||||
content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 98 97\r\n");
|
||||
}else if ("TCP-ACTIVE".equals(streamMode)) {
|
||||
content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 98 97\r\n");
|
||||
}else if("UDP".equals(streamMode)) {
|
||||
content.append("m=video "+ mediaPort +" RTP/AVP 96 98 97\r\n");
|
||||
}
|
||||
content.append("a=recvonly\r\n");
|
||||
content.append("a=rtpmap:96 PS/90000\r\n");
|
||||
content.append("a=rtpmap:98 H264/90000\r\n");
|
||||
content.append("a=rtpmap:97 MPEG4/90000\r\n");
|
||||
if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式
|
||||
content.append("a=setup:passive\r\n");
|
||||
content.append("a=connection:new\r\n");
|
||||
}else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式
|
||||
content.append("a=setup:active\r\n");
|
||||
content.append("a=connection:new\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
content.append("y="+ssrc+"\r\n");//ssrc
|
||||
|
||||
Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, "playback", null);
|
||||
|
||||
ClientTransaction transaction = transmitRequest(device, request);
|
||||
streamSession.put(ssrc, transaction);
|
||||
|
||||
ClientTransaction transaction = transmitRequest(device, request, errorEvent);
|
||||
streamSession.put(streamId, transaction);
|
||||
|
||||
} catch ( SipException | ParseException | InvalidArgumentException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 视频流停止
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public void streamByeCmd(String ssrc) {
|
||||
streamByeCmd(ssrc, null);
|
||||
}
|
||||
@Override
|
||||
public void streamByeCmd(String streamId, SipSubscribe.Event okEvent) {
|
||||
|
||||
try {
|
||||
ClientTransaction transaction = streamSession.get(ssrc);
|
||||
ClientTransaction transaction = streamSession.get(streamId);
|
||||
// 服务重启后
|
||||
if (transaction == null) {
|
||||
StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId);
|
||||
if (streamInfo != null) {
|
||||
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -436,6 +501,9 @@ public class SIPCommander implements ISIPCommander {
|
||||
if (dialog == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Request byeRequest = dialog.createRequest(Request.BYE);
|
||||
SipURI byeURI = (SipURI) byeRequest.getRequestURI();
|
||||
String vh = transaction.getRequest().getHeader(ViaHeader.NAME).toString();
|
||||
@@ -452,8 +520,16 @@ public class SIPCommander implements ISIPCommander {
|
||||
} else if("UDP".equals(protocol)) {
|
||||
clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest);
|
||||
}
|
||||
|
||||
CallIdHeader callIdHeader = (CallIdHeader) byeRequest.getHeader(CallIdHeader.NAME);
|
||||
if (okEvent != null) {
|
||||
sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), okEvent);
|
||||
}
|
||||
|
||||
dialog.sendRequest(clientTransaction);
|
||||
streamSession.remove(ssrc);
|
||||
|
||||
streamSession.remove(streamId);
|
||||
zlmrtpServerFactory.closeRTPServer(streamId);
|
||||
} catch (TransactionDoesNotExistException e) {
|
||||
e.printStackTrace();
|
||||
} catch (SipException e) {
|
||||
@@ -571,6 +647,7 @@ public class SIPCommander implements ISIPCommander {
|
||||
catalogXml.append("</Query>\r\n");
|
||||
|
||||
Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "ViaDeviceInfoBranch", "FromDeviceInfoTag", "ToDeviceInfoTag");
|
||||
|
||||
transmitRequest(device, request);
|
||||
|
||||
} catch (SipException | ParseException | InvalidArgumentException e) {
|
||||
@@ -586,7 +663,7 @@ public class SIPCommander implements ISIPCommander {
|
||||
* @param device 视频设备
|
||||
*/
|
||||
@Override
|
||||
public boolean catalogQuery(Device device) {
|
||||
public boolean catalogQuery(Device device, SipSubscribe.Event errorEvent) {
|
||||
// 清空通道
|
||||
storager.cleanChannelsForDevice(device.getDeviceId());
|
||||
try {
|
||||
@@ -598,8 +675,9 @@ public class SIPCommander implements ISIPCommander {
|
||||
catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
|
||||
catalogXml.append("</Query>\r\n");
|
||||
|
||||
Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "ViaCatalogBranch", "FromCatalogTag", "ToCatalogTag");
|
||||
transmitRequest(device, request);
|
||||
Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "ViaCatalogBranch", "FromCatalogTag", null);
|
||||
|
||||
transmitRequest(device, request, errorEvent);
|
||||
} catch (SipException | ParseException | InvalidArgumentException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
@@ -631,7 +709,8 @@ public class SIPCommander implements ISIPCommander {
|
||||
recordInfoXml.append("<Type>all</Type>\r\n");
|
||||
recordInfoXml.append("</Query>\r\n");
|
||||
|
||||
Request request = headerProvider.createMessageRequest(device, recordInfoXml.toString(), "ViaRecordInfoBranch", "FromRecordInfoTag", "ToRecordInfoTag");
|
||||
Request request = headerProvider.createMessageRequest(device, recordInfoXml.toString(), "ViaRecordInfoBranch", "FromRecordInfoTag", null);
|
||||
|
||||
transmitRequest(device, request);
|
||||
} catch (SipException | ParseException | InvalidArgumentException e) {
|
||||
e.printStackTrace();
|
||||
@@ -683,17 +762,45 @@ public class SIPCommander implements ISIPCommander {
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private ClientTransaction transmitRequest(Device device, Request request) throws SipException {
|
||||
return transmitRequest(device, request, null, null);
|
||||
}
|
||||
|
||||
private ClientTransaction transmitRequest(Device device, Request request, SipSubscribe.Event errorEvent) throws SipException {
|
||||
return transmitRequest(device, request, errorEvent, null);
|
||||
}
|
||||
|
||||
private ClientTransaction transmitRequest(Device device, Request request, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws SipException {
|
||||
ClientTransaction clientTransaction = null;
|
||||
if("TCP".equals(device.getTransport())) {
|
||||
clientTransaction = tcpSipProvider.getNewClientTransaction(request);
|
||||
} else if("UDP".equals(device.getTransport())) {
|
||||
clientTransaction = udpSipProvider.getNewClientTransaction(request);
|
||||
}
|
||||
|
||||
CallIdHeader callIdHeader = (CallIdHeader)request.getHeader(CallIdHeader.NAME);
|
||||
// 添加错误订阅
|
||||
if (errorEvent != null) {
|
||||
sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), errorEvent);
|
||||
}
|
||||
// 添加订阅
|
||||
if (okEvent != null) {
|
||||
sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), okEvent);
|
||||
}
|
||||
|
||||
clientTransaction.sendRequest();
|
||||
return clientTransaction;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void closeRTPServer(Device device, String channelId) {
|
||||
if (rtpEnable) {
|
||||
String streamId = String.format("gb_play_%s_%s", device.getDeviceId(), channelId);
|
||||
zlmrtpServerFactory.closeRTPServer(streamId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import javax.sip.SipException;
|
||||
import javax.sip.message.Request;
|
||||
import javax.sip.message.Response;
|
||||
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
import org.dom4j.Document;
|
||||
import org.dom4j.DocumentException;
|
||||
import org.dom4j.Element;
|
||||
@@ -48,6 +49,8 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
|
||||
|
||||
private IVideoManagerStorager storager;
|
||||
|
||||
private IRedisCatchStorage redisCatchStorage;
|
||||
|
||||
private EventPublisher publisher;
|
||||
|
||||
private RedisUtil redis;
|
||||
@@ -294,7 +297,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
|
||||
device.setStreamMode("UDP");
|
||||
}
|
||||
storager.updateDevice(device);
|
||||
cmder.catalogQuery(device);
|
||||
cmder.catalogQuery(device, null);
|
||||
// 回复200 OK
|
||||
responseAck(evt);
|
||||
if (offLineDetector.isOnline(deviceId)) {
|
||||
@@ -315,12 +318,16 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
|
||||
try {
|
||||
Element rootElement = getRootElement(evt);
|
||||
String deviceId = XmlUtil.getText(rootElement, "DeviceID");
|
||||
// 回复200 OK
|
||||
responseAck(evt);
|
||||
if (offLineDetector.isOnline(deviceId)) {
|
||||
publisher.onlineEventPublish(deviceId, VideoManagerConstants.EVENT_ONLINE_KEEPLIVE);
|
||||
} else {
|
||||
// 检查设备是否存在, 不存在则不回复
|
||||
if (storager.exists(deviceId)) {
|
||||
// 回复200 OK
|
||||
responseAck(evt);
|
||||
if (offLineDetector.isOnline(deviceId)) {
|
||||
publisher.onlineEventPublish(deviceId, VideoManagerConstants.EVENT_ONLINE_KEEPLIVE);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
} catch (ParseException | SipException | InvalidArgumentException | DocumentException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@@ -447,10 +454,10 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
|
||||
String NotifyType =XmlUtil.getText(rootElement, "NotifyType");
|
||||
if (NotifyType.equals("121")){
|
||||
logger.info("媒体播放完毕,通知关流");
|
||||
StreamInfo streamInfo = storager.queryPlaybackByDevice(deviceId, "*");
|
||||
StreamInfo streamInfo = redisCatchStorage.queryPlaybackByDevice(deviceId, "*");
|
||||
if (streamInfo != null) {
|
||||
storager.stopPlayback(streamInfo);
|
||||
cmder.streamByeCmd(streamInfo.getSsrc());
|
||||
redisCatchStorage.stopPlayback(streamInfo);
|
||||
cmder.streamByeCmd(streamInfo.getStreamId());
|
||||
}
|
||||
}
|
||||
} catch (ParseException | SipException | InvalidArgumentException | DocumentException e) {
|
||||
@@ -503,4 +510,11 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
|
||||
this.offLineDetector = offLineDetector;
|
||||
}
|
||||
|
||||
public IRedisCatchStorage getRedisCatchStorage() {
|
||||
return redisCatchStorage;
|
||||
}
|
||||
|
||||
public void setRedisCatchStorage(IRedisCatchStorage redisCatchStorage) {
|
||||
this.redisCatchStorage = redisCatchStorage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,17 +107,15 @@ public class RegisterRequestProcessor extends SIPRequestAbstractProcessor {
|
||||
rPort = viaHeader.getPort();
|
||||
}
|
||||
//
|
||||
Host host = new Host();
|
||||
host.setIp(received);
|
||||
host.setPort(rPort);
|
||||
host.setAddress(received.concat(":").concat(String.valueOf(rPort)));
|
||||
AddressImpl address = (AddressImpl) fromHeader.getAddress();
|
||||
SipUri uri = (SipUri) address.getURI();
|
||||
String deviceId = uri.getUser();
|
||||
device = new Device();
|
||||
device.setStreamMode("UDP");
|
||||
device.setDeviceId(deviceId);
|
||||
device.setHost(host);
|
||||
device.setIp(received);
|
||||
device.setPort(rPort);
|
||||
device.setHostAddress(received.concat(":").concat(String.valueOf(rPort)));
|
||||
// 注销成功
|
||||
if (expiresHeader != null && expiresHeader.getExpires() == 0) {
|
||||
registerFlag = 2;
|
||||
@@ -141,9 +139,15 @@ public class RegisterRequestProcessor extends SIPRequestAbstractProcessor {
|
||||
// 下发catelog查询目录
|
||||
if (registerFlag == 1 && device != null) {
|
||||
logger.info("注册成功! deviceId:" + device.getDeviceId());
|
||||
boolean exists = storager.exists(device.getDeviceId());
|
||||
device.setRegisterTimeMillis(System.currentTimeMillis());
|
||||
storager.updateDevice(device);
|
||||
publisher.onlineEventPublish(device.getDeviceId(), VideoManagerConstants.EVENT_ONLINE_REGISTER);
|
||||
handler.onRegister(device);
|
||||
|
||||
// 只有第一次注册才更新通道
|
||||
if (!exists) {
|
||||
handler.onRegister(device);
|
||||
}
|
||||
} else if (registerFlag == 2) {
|
||||
logger.info("注销成功! deviceId:" + device.getDeviceId());
|
||||
publisher.outlineEventPublish(device.getDeviceId(), VideoManagerConstants.EVENT_OUTLINE_UNREGISTER);
|
||||
|
||||
@@ -12,6 +12,7 @@ import javax.sip.header.ViaHeader;
|
||||
import javax.sip.message.Request;
|
||||
import javax.sip.message.Response;
|
||||
|
||||
import gov.nist.javax.sip.header.CSeq;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -23,14 +24,14 @@ import com.genersoft.iot.vmp.gb28181.transmit.response.ISIPResponseProcessor;
|
||||
|
||||
|
||||
/**
|
||||
* @Description:处理INVITE响应
|
||||
* @Description:处理INVITE响应
|
||||
* @author: swwheihei
|
||||
* @date: 2020年5月3日 下午4:43:52
|
||||
* @date: 2020年5月3日 下午4:43:52
|
||||
*/
|
||||
@Component
|
||||
public class InviteResponseProcessor implements ISIPResponseProcessor {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(SIPProcessorFactory.class);
|
||||
private final static Logger logger = LoggerFactory.getLogger(InviteResponseProcessor.class);
|
||||
|
||||
/**
|
||||
* 处理invite响应
|
||||
@@ -49,48 +50,16 @@ public class InviteResponseProcessor implements ISIPResponseProcessor {
|
||||
// 成功响应
|
||||
// 下发ack
|
||||
if (statusCode == Response.OK) {
|
||||
// ClientTransaction clientTransaction = evt.getClientTransaction();
|
||||
// if(clientTransaction == null){
|
||||
// logger.error("回复ACK时,clientTransaction为null >>> {}",response);
|
||||
// return;
|
||||
// }
|
||||
// Dialog clientDialog = clientTransaction.getDialog();
|
||||
|
||||
// CSeqHeader clientCSeqHeader = (CSeqHeader)
|
||||
// response.getHeader(CSeqHeader.NAME);
|
||||
// long cseqId = clientCSeqHeader.getSeqNumber();
|
||||
// /*
|
||||
// createAck函数,创建的ackRequest,会采用Invite响应的200OK,中的contact字段中的地址,作为目标地址。
|
||||
// 有的终端传上来的可能还是内网地址,会造成ack发送不出去。接受不到音视频流
|
||||
// 所以在此处统一替换地址。和响应消息的Via头中的地址保持一致。
|
||||
// */
|
||||
// Request ackRequest = clientDialog.createAck(cseqId);
|
||||
// SipURI requestURI = (SipURI) ackRequest.getRequestURI();
|
||||
// ViaHeader viaHeader = (ViaHeader) response.getHeader(ViaHeader.NAME);
|
||||
// try {
|
||||
// requestURI.setHost(viaHeader.getHost());
|
||||
// } catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// requestURI.setPort(viaHeader.getPort());
|
||||
// clientDialog.sendAck(ackRequest);
|
||||
|
||||
Dialog dialog = evt.getDialog();
|
||||
CSeqHeader cseq = (CSeqHeader) response.getHeader(CSeqHeader.NAME);
|
||||
Request reqAck = dialog.createAck(cseq.getSeqNumber());
|
||||
|
||||
SipURI requestURI = (SipURI) reqAck.getRequestURI();
|
||||
ViaHeader viaHeader = (ViaHeader) response.getHeader(ViaHeader.NAME);
|
||||
// String viaHost =viaHeader.getHost();
|
||||
//getHost()函数取回的IP地址是“[xxx.xxx.xxx.xxx:yyyy]”的格式,需用正则表达式截取为“xxx.xxx.xxx.xxx"格式
|
||||
// Pattern p = Pattern.compile("(?<=//|)((\\w)+\\.)+\\w+");
|
||||
// Matcher matcher = p.matcher(viaHeader.getHost());
|
||||
// if (matcher.find()) {
|
||||
// requestURI.setHost(matcher.group());
|
||||
// }
|
||||
requestURI.setHost(viaHeader.getHost());
|
||||
requestURI.setPort(viaHeader.getPort());
|
||||
reqAck.setRequestURI(requestURI);
|
||||
|
||||
dialog.sendAck(reqAck);
|
||||
}
|
||||
} catch (InvalidArgumentException | SipException e) {
|
||||
|
||||
Reference in New Issue
Block a user