完善ssrc符合国标,并完善很多小问题

This commit is contained in:
songww
2020-05-10 22:33:21 +08:00
parent 2e778e342a
commit 3a502b36a8
16 changed files with 406 additions and 73 deletions

View File

@@ -71,6 +71,16 @@ public interface ISIPCommander {
*/
public String playStreamCmd(Device device,String channelId);
/**
* 请求回放视频流
*
* @param device 视频设备
* @param channelId 预览通道
* @param startTime 开始时间,格式要求yyyy-MM-dd HH:mm:ss
* @param endTime 结束时间,格式要求yyyy-MM-dd HH:mm:ss
*/
public String playbackStreamCmd(Device device,String channelId, String recordId, String startTime, String endTime);
/**
* 语音广播
*
@@ -153,7 +163,7 @@ public interface ISIPCommander {
* @param startTime 开始时间,格式要求yyyy-MM-dd HH:mm:ss
* @param endTime 结束时间,格式要求yyyy-MM-dd HH:mm:ss
*/
public boolean recordInfoQuery(Device device, String startTime, String endTime);
public boolean recordInfoQuery(Device device, String channelId, String startTime, String endTime);
/**
* 查询报警信息

View File

@@ -35,7 +35,7 @@ public class SIPRequestHeaderProvider {
private SipLayer layer;
@Autowired
private SipConfig config;
private SipConfig sipConfig;
public Request createMessageRequest(Device device, String content, String viaTag, String fromTag, String toTag) throws ParseException, InvalidArgumentException {
Request request = null;
@@ -44,12 +44,12 @@ public class SIPRequestHeaderProvider {
SipURI requestURI = layer.getAddressFactory().createSipURI(device.getDeviceId(), host.getAddress());
// via
ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
ViaHeader viaHeader = layer.getHeaderFactory().createViaHeader(config.getSipIp(), config.getSipPort(),
ViaHeader viaHeader = layer.getHeaderFactory().createViaHeader(sipConfig.getSipIp(), sipConfig.getSipPort(),
device.getTransport(), viaTag);
viaHeaders.add(viaHeader);
// from
SipURI fromSipURI = layer.getAddressFactory().createSipURI(device.getDeviceId(),
config.getSipIp() + ":" + config.getSipPort());
sipConfig.getSipIp() + ":" + sipConfig.getSipPort());
Address fromAddress = layer.getAddressFactory().createAddress(fromSipURI);
FromHeader fromHeader = layer.getHeaderFactory().createFromHeader(fromAddress, fromTag);
// to
@@ -78,11 +78,11 @@ public class SIPRequestHeaderProvider {
SipURI requestLine = layer.getAddressFactory().createSipURI(device.getDeviceId(), host.getAddress());
//via
ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
ViaHeader viaHeader = layer.getHeaderFactory().createViaHeader(config.getSipIp(), config.getSipPort(), device.getTransport(), viaTag);
ViaHeader viaHeader = layer.getHeaderFactory().createViaHeader(sipConfig.getSipIp(), sipConfig.getSipPort(), device.getTransport(), viaTag);
viaHeader.setRPort();
viaHeaders.add(viaHeader);
//from
SipURI fromSipURI = layer.getAddressFactory().createSipURI(device.getDeviceId(),config.getSipIp()+":"+config.getSipPort());
SipURI fromSipURI = layer.getAddressFactory().createSipURI(device.getDeviceId(),sipConfig.getSipIp()+":"+sipConfig.getSipPort());
Address fromAddress = layer.getAddressFactory().createAddress(fromSipURI);
FromHeader fromHeader = layer.getHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记否则无法创建会话无法回应ack
//to

View File

@@ -17,6 +17,9 @@ import com.genersoft.iot.vmp.gb28181.bean.Device;
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.DateUtil;
import com.genersoft.iot.vmp.gb28181.utils.SsrcUtil;
import tk.mybatis.mapper.util.StringUtil;
/**
* @Description:设备能力接口,用于定义设备的控制、查询能力
@@ -27,7 +30,7 @@ import com.genersoft.iot.vmp.gb28181.utils.DateUtil;
public class SIPCommander implements ISIPCommander {
@Autowired
private SipConfig config;
private SipConfig sipConfig;
@Autowired
private SIPRequestHeaderProvider headerProvider;
@@ -46,7 +49,7 @@ public class SIPCommander implements ISIPCommander {
*/
@Override
public boolean ptzdirectCmd(Device device, String channelId, int leftRight, int upDown) {
return ptzCmd(device, channelId, leftRight, upDown, 0, config.getSpeed(), 0);
return ptzCmd(device, channelId, leftRight, upDown, 0, sipConfig.getSpeed(), 0);
}
/**
@@ -72,7 +75,7 @@ public class SIPCommander implements ISIPCommander {
*/
@Override
public boolean ptzZoomCmd(Device device, String channelId, int inOut) {
return ptzCmd(device, channelId, 0, 0, inOut, 0, config.getSpeed());
return ptzCmd(device, channelId, 0, 0, inOut, 0, sipConfig.getSpeed());
}
/**
@@ -135,23 +138,19 @@ public class SIPCommander implements ISIPCommander {
public String playStreamCmd(Device device, String channelId) {
try {
//生成ssrc标识数据流 10位数字
String ssrc = "";
Random random = new Random();
// ZLMediaServer最大识别7FFFFFFF即2147483647所以随机数不能超过这个数
ssrc = String.valueOf(random.nextInt(2147483647));
String ssrc = SsrcUtil.getPlaySsrc();
//
StringBuffer content = new StringBuffer(200);
content.append("v=0\r\n");
content.append("o="+channelId+" 0 0 IN IP4 "+config.getSipIp()+"\r\n");
content.append("o="+channelId+" 0 0 IN IP4 "+sipConfig.getSipIp()+"\r\n");
content.append("s=Play\r\n");
content.append("c=IN IP4 "+config.getMediaIp()+"\r\n");
content.append("c=IN IP4 "+sipConfig.getMediaIp()+"\r\n");
content.append("t=0 0\r\n");
if(device.getTransport().equals("TCP")) {
content.append("m=video "+config.getMediaPort()+" TCP/RTP/AVP 96 98 97\r\n");
content.append("m=video "+sipConfig.getMediaPort()+" TCP/RTP/AVP 96 98 97\r\n");
}
if(device.getTransport().equals("UDP")) {
content.append("m=video "+config.getMediaPort()+" RTP/AVP 96 98 97\r\n");
content.append("m=video "+sipConfig.getMediaPort()+" RTP/AVP 96 98 97\r\n");
}
content.append("a=sendrecv\r\n");
content.append("a=rtpmap:96 PS/90000\r\n");
@@ -172,6 +171,53 @@ public class SIPCommander implements ISIPCommander {
return null;
}
}
/**
* 请求回放视频流
*
* @param device 视频设备
* @param channelId 预览通道
* @param startTime 开始时间,格式要求yyyy-MM-dd HH:mm:ss
* @param endTime 结束时间,格式要求yyyy-MM-dd HH:mm:ss
*/
@Override
public String playbackStreamCmd(Device device, String channelId, String recordId, String startTime, String endTime) {
try {
String ssrc = SsrcUtil.getPlayBackSsrc();
//
StringBuffer content = new StringBuffer(200);
content.append("v=0\r\n");
content.append("o="+channelId+" 0 0 IN IP4 "+sipConfig.getSipIp()+"\r\n");
content.append("s=Playback\r\n");
content.append("u="+recordId+":3\r\n");
content.append("c=IN IP4 "+sipConfig.getMediaIp()+"\r\n");
content.append("t="+DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime)+" "+DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime) +"\r\n");
if(device.getTransport().equals("TCP")) {
content.append("m=video "+sipConfig.getMediaPort()+" TCP/RTP/AVP 96 98 97\r\n");
}
if(device.getTransport().equals("UDP")) {
content.append("m=video "+sipConfig.getMediaPort()+" 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(device.getTransport().equals("TCP")){
content.append("a=setup:passive\r\n");
content.append("a=connection:new\r\n");
}
content.append("y="+ssrc+"\r\n");//ssrc
Request request = headerProvider.createInviteRequest(device, content.toString(), null, "live", null);
transmitRequest(device, request);
return ssrc;
} catch ( SipException | ParseException | InvalidArgumentException e) {
e.printStackTrace();
return null;
}
}
/**
* 语音广播
@@ -323,22 +369,23 @@ public class SIPCommander implements ISIPCommander {
* @param endTime 结束时间,格式要求yyyy-MM-dd HH:mm:ss
*/
@Override
public boolean recordInfoQuery(Device device, String startTime, String endTime) {
public boolean recordInfoQuery(Device device, String channelId, String startTime, String endTime) {
try {
StringBuffer catalogXml = new StringBuffer(200);
catalogXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>");
catalogXml.append("<Query>");
catalogXml.append("<CmdType>RecordInfo</CmdType>");
catalogXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>");
catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>");
catalogXml.append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(startTime) + "</StartTime>");
catalogXml.append("<EndTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(endTime) + "</EndTime>");
StringBuffer recordInfoXml = new StringBuffer(200);
recordInfoXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>");
recordInfoXml.append("<Query>");
recordInfoXml.append("<CmdType>RecordInfo</CmdType>");
recordInfoXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>");
recordInfoXml.append("<DeviceID>" + channelId + "</DeviceID>");
recordInfoXml.append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(startTime) + "</StartTime>");
recordInfoXml.append("<EndTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(endTime) + "</EndTime>");
recordInfoXml.append("<Secrecy>0</Secrecy>");
// 大华NVR要求必须增加一个值为all的文本元素节点Type
catalogXml.append("<Type>all</Type>");
catalogXml.append("</Query>");
recordInfoXml.append("<Type>all</Type>");
recordInfoXml.append("</Query>");
Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "ViaRecordInfoBranch", "FromRecordInfoTag", "ToRecordInfoTag");
Request request = headerProvider.createMessageRequest(device, recordInfoXml.toString(), "ViaRecordInfoBranch", "FromRecordInfoTag", "ToRecordInfoTag");
transmitRequest(device, request);
} catch (SipException | ParseException | InvalidArgumentException e) {
e.printStackTrace();
@@ -398,4 +445,5 @@ public class SIPCommander implements ISIPCommander {
sipLayer.getUdpSipProvider().sendRequest(request);
}
}
}

View File

@@ -19,6 +19,8 @@ import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -36,6 +38,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.request.ISIPRequestProcessor;
import com.genersoft.iot.vmp.gb28181.utils.DateUtil;
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
/**
* @Description:MESSAGE请求处理器
@@ -44,7 +47,9 @@ import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
*/
@Component
public class MessageRequestProcessor implements ISIPRequestProcessor {
private final static Logger logger = LoggerFactory.getLogger(MessageRequestProcessor.class);
private ServerTransaction transaction;
private SipLayer layer;
@@ -58,9 +63,14 @@ public class MessageRequestProcessor implements ISIPRequestProcessor {
@Autowired
private EventPublisher publisher;
@Autowired
private RedisUtil redis;
@Autowired
private DeferredResultHolder deferredResultHolder;
private final static String CACHE_RECORDINFO_KEY = "CACHE_RECORDINFO_";
/**
* 处理MESSAGE请求
*
@@ -77,14 +87,19 @@ public class MessageRequestProcessor implements ISIPRequestProcessor {
Request request = evt.getRequest();
if (new String(request.getRawContent()).contains("<CmdType>Keepalive</CmdType>")) {
logger.info("接收到KeepAlive消息");
processMessageKeepAlive(evt);
} else if (new String(request.getRawContent()).contains("<CmdType>Catalog</CmdType>")) {
logger.info("接收到Catalog消息");
processMessageCatalogList(evt);
} else if (new String(request.getRawContent()).contains("<CmdType>DeviceInfo</CmdType>")) {
logger.info("接收到DeviceInfo消息");
processMessageDeviceInfo(evt);
} else if (new String(request.getRawContent()).contains("<CmdType>Alarm</CmdType>")) {
logger.info("接收到Alarm消息");
processMessageAlarm(evt);
} else if (new String(request.getRawContent()).contains("<CmdType>recordInfo</CmdType>")) {
} else if (new String(request.getRawContent()).contains("<CmdType>RecordInfo</CmdType>")) {
logger.info("接收到RecordInfo消息");
processMessageRecordInfo(evt);
}
@@ -245,6 +260,7 @@ public class MessageRequestProcessor implements ISIPRequestProcessor {
/***
* 收到catalog设备目录列表请求 处理
* TODO 过期时间暂时写死180秒后续与DeferredResult超时时间保持一致
* @param evt
*/
private void processMessageRecordInfo(RequestEvent evt) {
@@ -256,15 +272,15 @@ public class MessageRequestProcessor implements ISIPRequestProcessor {
recordInfo.setDeviceId(deviceId);
recordInfo.setName(XmlUtil.getText(rootElement,"Name"));
recordInfo.setSumNum(Integer.parseInt(XmlUtil.getText(rootElement,"SumNum")));
String sn = XmlUtil.getText(rootElement,"SN");
Element recordListElement = rootElement.element("RecordList");
if (recordListElement == null) {
return;
}
Iterator<Element> recordListIterator = recordListElement.elementIterator();
List<RecordItem> recordList = new ArrayList<RecordItem>();
if (recordListIterator != null) {
List<RecordItem> recordList = new ArrayList<RecordItem>();
RecordItem record = new RecordItem();
// 遍历DeviceList
while (recordListIterator.hasNext()) {
@@ -273,6 +289,7 @@ public class MessageRequestProcessor implements ISIPRequestProcessor {
if (recordElement == null) {
continue;
}
record = new RecordItem();
record.setDeviceId(XmlUtil.getText(itemRecord,"DeviceID"));
record.setName(XmlUtil.getText(itemRecord,"Name"));
record.setFilePath(XmlUtil.getText(itemRecord,"FilePath"));
@@ -281,13 +298,42 @@ public class MessageRequestProcessor implements ISIPRequestProcessor {
record.setEndTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(XmlUtil.getText(itemRecord,"EndTime")));
record.setSecrecy(itemRecord.element("Secrecy") == null? 0:Integer.parseInt(XmlUtil.getText(itemRecord,"Secrecy")));
record.setType(XmlUtil.getText(itemRecord,"Type"));
record.setRecordId(XmlUtil.getText(itemRecord,"RecordID"));
record.setRecordId(XmlUtil.getText(itemRecord,"RecorderID"));
recordList.add(record);
}
recordInfo.setRecordList(recordList);
}
// 存在录像且如果当前录像明细个数小于总条数,说明拆包返回,需要组装,暂不返回
if (recordInfo.getSumNum() > 0 && recordList.size() > 0 && recordList.size() < recordInfo.getSumNum()) {
// 为防止连续请求该设备的录像数据返回数据错乱特增加sn进行区分
String cacheKey = CACHE_RECORDINFO_KEY+deviceId+sn;
// TODO 暂时直接操作redis存储后续封装专用缓存接口改为本地内存缓存
if (redis.hasKey(cacheKey)) {
List<RecordItem> previousList = (List<RecordItem>) redis.get(cacheKey);
if (previousList != null && previousList.size() > 0) {
recordList.addAll(previousList);
}
// 本分支表示录像列表被拆包,且加上之前的数据还是不够,保存缓存返回,等待下个包再处理
if (recordList.size() < recordInfo.getSumNum()) {
redis.set(cacheKey, recordList, 180);
return;
} else {
// 本分支表示录像被拆包,但加上之前的数据够足够,返回响应
// 因设备心跳有监听redis过期机制为提高性能此处手动删除
redis.del(cacheKey);
}
} else {
// 本分支有两种可能1、录像列表被拆包且是第一个包,直接保存缓存返回,等待下个包再处理
// 2、之前有包但超时清空了那么这次sn批次的响应数据已经不完整等待过期时间后redis自动清空数据
redis.set(cacheKey, recordList, 180);
return;
}
}
// 走到这里有以下可能1、没有录像信息,第一次收到recordinfo的消息即返回响应数据无redis操作
// 2、有录像数据且第一次即收到完整数据返回响应数据无redis操作
// 3、有录像数据在超时时间内收到多次包组装后数量足够返回数据
RequestMessage msg = new RequestMessage();
msg.setDeviceId(deviceId);
msg.setType(DeferredResultHolder.CALLBACK_CMD_RECORDINFO);

View File

@@ -45,7 +45,7 @@ import gov.nist.javax.sip.header.Expires;
public class RegisterRequestProcessor implements ISIPRequestProcessor {
@Autowired
private SipConfig config;
private SipConfig sipConfig;
@Autowired
private RegisterLogicHandler handler;
@@ -77,7 +77,7 @@ public class RegisterRequestProcessor implements ISIPRequestProcessor {
// 校验密码是否正确
if (authorhead != null) {
passwordCorrect = new DigestServerAuthenticationHelper().doAuthenticatePlainTextPassword(request,
config.getSipPassword());
sipConfig.getSipPassword());
}
// 未携带授权头或者密码错误 回复401
@@ -89,7 +89,7 @@ public class RegisterRequestProcessor implements ISIPRequestProcessor {
System.out.println("密码错误 回复401");
}
response = layer.getMessageFactory().createResponse(Response.UNAUTHORIZED, request);
new DigestServerAuthenticationHelper().generateChallenge(layer.getHeaderFactory(), response, config.getSipDomain());
new DigestServerAuthenticationHelper().generateChallenge(layer.getHeaderFactory(), response, sipConfig.getSipDomain());
}
// 携带授权头并且密码正确
else if (passwordCorrect) {