forked from Thirdparty/wvp
完善ssrc符合国标,并完善很多小问题
parent
2e778e342a
commit
3a502b36a8
|
@ -3,7 +3,7 @@ package com.genersoft.iot.vmp.conf;
|
|||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@Configuration("sipConfig")
|
||||
public class SipConfig {
|
||||
|
||||
@Value("${sip.ip}")
|
||||
|
|
|
@ -42,7 +42,7 @@ public class SipLayer implements SipListener, Runnable {
|
|||
private final static Logger logger = LoggerFactory.getLogger(SipLayer.class);
|
||||
|
||||
@Autowired
|
||||
private SipConfig config;
|
||||
private SipConfig sipConfig;
|
||||
|
||||
private SipProvider tcpSipProvider;
|
||||
|
||||
|
@ -77,7 +77,7 @@ public class SipLayer implements SipListener, Runnable {
|
|||
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("javax.sip.STACK_NAME", "GB28181_SIP");
|
||||
properties.setProperty("javax.sip.IP_ADDRESS", config.getSipIp());
|
||||
properties.setProperty("javax.sip.IP_ADDRESS", sipConfig.getSipIp());
|
||||
properties.setProperty("gov.nist.javax.sip.LOG_MESSAGE_CONTENT", "false");
|
||||
/**
|
||||
* sip_server_log.log 和 sip_debug_log.log public static final int TRACE_NONE =
|
||||
|
@ -92,20 +92,20 @@ public class SipLayer implements SipListener, Runnable {
|
|||
startTcpListener();
|
||||
startUdpListener();
|
||||
} catch (Exception e) {
|
||||
logger.error("Sip Server 启动失败! port {" + config.getSipPort() + "}");
|
||||
logger.error("Sip Server 启动失败! port {" + sipConfig.getSipPort() + "}");
|
||||
e.printStackTrace();
|
||||
}
|
||||
logger.info("Sip Server 启动成功 port {" + config.getSipPort() + "}");
|
||||
logger.info("Sip Server 启动成功 port {" + sipConfig.getSipPort() + "}");
|
||||
}
|
||||
|
||||
private void startTcpListener() throws Exception {
|
||||
ListeningPoint tcpListeningPoint = sipStack.createListeningPoint(config.getSipIp(), config.getSipPort(), "TCP");
|
||||
ListeningPoint tcpListeningPoint = sipStack.createListeningPoint(sipConfig.getSipIp(), sipConfig.getSipPort(), "TCP");
|
||||
tcpSipProvider = sipStack.createSipProvider(tcpListeningPoint);
|
||||
tcpSipProvider.addSipListener(this);
|
||||
}
|
||||
|
||||
private void startUdpListener() throws Exception {
|
||||
ListeningPoint udpListeningPoint = sipStack.createListeningPoint(config.getSipIp(), config.getSipPort(), "UDP");
|
||||
ListeningPoint udpListeningPoint = sipStack.createListeningPoint(sipConfig.getSipIp(), sipConfig.getSipPort(), "UDP");
|
||||
udpSipProvider = sipStack.createSipProvider(udpListeningPoint);
|
||||
udpSipProvider.addSipListener(this);
|
||||
}
|
||||
|
@ -126,7 +126,7 @@ public class SipLayer implements SipListener, Runnable {
|
|||
int status = response.getStatusCode();
|
||||
if ((status >= 200) && (status < 300)) { // Success!
|
||||
ISIPResponseProcessor processor = processorFactory.createResponseProcessor(evt);
|
||||
processor.process(evt, this, config);
|
||||
processor.process(evt, this, sipConfig);
|
||||
} else {
|
||||
logger.warn("接收到失败的response响应!status:" + status + ",message:" + response.getContent().toString());
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ public class RecordItem {
|
|||
|
||||
private String type;
|
||||
|
||||
private String recordId;
|
||||
private String recorderId;
|
||||
|
||||
public String getDeviceId() {
|
||||
return deviceId;
|
||||
|
@ -81,12 +81,12 @@ public class RecordItem {
|
|||
this.type = type;
|
||||
}
|
||||
|
||||
public String getRecordId() {
|
||||
return recordId;
|
||||
public String getRecorderId() {
|
||||
return recorderId;
|
||||
}
|
||||
|
||||
public void setRecordId(String recordId) {
|
||||
this.recordId = recordId;
|
||||
public void setRecordId(String recorderId) {
|
||||
this.recorderId = recorderId;
|
||||
}
|
||||
|
||||
public String getEndTime() {
|
||||
|
|
|
@ -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);
|
||||
|
||||
/**
|
||||
* 查询报警信息
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.utils;
|
|||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
|
@ -11,7 +12,8 @@ import java.util.Locale;
|
|||
*/
|
||||
public class DateUtil {
|
||||
|
||||
private static final String yyyy_MM_dd_T_HH_mm_ss_SSSXXX = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
|
||||
//private static final String yyyy_MM_dd_T_HH_mm_ss_SSSXXX = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
|
||||
private static final String yyyy_MM_dd_T_HH_mm_ss_SSSXXX = "yyyy-MM-dd'T'HH:mm:ss";
|
||||
private static final String yyyy_MM_dd_HH_mm_ss = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
public static String yyyy_MM_dd_HH_mm_ssToISO8601(String formatTime) {
|
||||
|
@ -37,4 +39,19 @@ public class DateUtil {
|
|||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static long yyyy_MM_dd_HH_mm_ssToTimestamp(String formatTime) {
|
||||
SimpleDateFormat format=new SimpleDateFormat(yyyy_MM_dd_HH_mm_ss);
|
||||
//设置要读取的时间字符串格式
|
||||
Date date;
|
||||
try {
|
||||
date = format.parse(formatTime);
|
||||
Long timestamp=date.getTime();
|
||||
//转换为Date类
|
||||
return timestamp;
|
||||
} catch (ParseException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
package com.genersoft.iot.vmp.gb28181.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import com.genersoft.iot.vmp.conf.SipConfig;
|
||||
import com.genersoft.iot.vmp.utils.SpringBeanFactory;
|
||||
|
||||
/**
|
||||
* @Description:SIP信令中的SSRC工具类。SSRC值由10位十进制整数组成的字符串,第一位为0代表实况,为1则代表回放;第二位至第六位由监控域ID的第4位到第8位组成;最后4位为不重复的4个整数
|
||||
* @author: songww
|
||||
* @date: 2020年5月10日 上午11:57:57
|
||||
*/
|
||||
public class SsrcUtil {
|
||||
|
||||
private static String ssrcPrefix;
|
||||
|
||||
private static List<String> isUsed;
|
||||
|
||||
private static List<String> notUsed;
|
||||
|
||||
private static void init() {
|
||||
SipConfig sipConfig = (SipConfig) SpringBeanFactory.getBean("sipConfig");
|
||||
ssrcPrefix = sipConfig.getSipDomain().substring(4, 9);
|
||||
isUsed = new ArrayList<String>();
|
||||
notUsed = new ArrayList<String>();
|
||||
for (int i = 1; i < 10000; i++) {
|
||||
if (i < 10) {
|
||||
notUsed.add("000" + i);
|
||||
} else if (i < 100) {
|
||||
notUsed.add("00" + i);
|
||||
} else if (i < 1000) {
|
||||
notUsed.add("0" + i);
|
||||
} else {
|
||||
notUsed.add(String.valueOf(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视频预览的SSRC值,第一位固定为0
|
||||
*
|
||||
*/
|
||||
public static String getPlaySsrc() {
|
||||
return "0" + getSsrcPrefix() + getSN();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取录像回放的SSRC值,第一位固定为1
|
||||
*
|
||||
*/
|
||||
public static String getPlayBackSsrc() {
|
||||
return "1" + getSsrcPrefix() + getSN();
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放ssrc,主要用完的ssrc一定要释放,否则会耗尽
|
||||
*
|
||||
*/
|
||||
public static void releaseSsrc(String ssrc) {
|
||||
String sn = ssrc.substring(6);
|
||||
isUsed.remove(sn);
|
||||
notUsed.add(sn);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取后四位数SN,随机数
|
||||
*
|
||||
*/
|
||||
private static String getSN() {
|
||||
String sn = null;
|
||||
if (notUsed.size() == 0) {
|
||||
throw new RuntimeException("ssrc已经用完");
|
||||
} else if (notUsed.size() == 1) {
|
||||
sn = notUsed.get(0);
|
||||
} else {
|
||||
sn = notUsed.get(new Random().nextInt(notUsed.size() - 1));
|
||||
}
|
||||
notUsed.remove(0);
|
||||
isUsed.add(sn);
|
||||
return sn;
|
||||
}
|
||||
|
||||
private static String getSsrcPrefix() {
|
||||
if (ssrcPrefix == null) {
|
||||
init();
|
||||
}
|
||||
return ssrcPrefix;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package com.genersoft.iot.vmp.utils;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @Description:spring bean获取工厂,获取spring中的已初始化的bean
|
||||
* @author: songww
|
||||
* @date: 2019年6月25日 下午4:51:52
|
||||
*
|
||||
*/
|
||||
@Component
|
||||
public class SpringBeanFactory implements ApplicationContextAware {
|
||||
|
||||
// Spring应用上下文环境
|
||||
private static ApplicationContext applicationContext;
|
||||
|
||||
/**
|
||||
* 实现ApplicationContextAware接口的回调方法,设置上下文环境
|
||||
*/
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext)
|
||||
throws BeansException {
|
||||
SpringBeanFactory.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public static ApplicationContext getApplicationContext() {
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对象 这里重写了bean方法,起主要作用
|
||||
*/
|
||||
public static Object getBean(String beanId) throws BeansException {
|
||||
return applicationContext.getBean(beanId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前环境
|
||||
*/
|
||||
public static String getActiveProfile() {
|
||||
return applicationContext.getEnvironment().getActiveProfiles()[0];
|
||||
}
|
||||
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
package com.genersoft.iot.vmp.vmanager.device;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
package com.genersoft.iot.vmp.vmanager.playback;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
|
||||
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class PlaybackController {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(PlaybackController.class);
|
||||
|
||||
@Autowired
|
||||
private SIPCommander cmder;
|
||||
|
||||
@Autowired
|
||||
private IVideoManagerStorager storager;
|
||||
|
||||
@GetMapping("/playback/{deviceId}/{channelId}")
|
||||
public ResponseEntity<String> play(@PathVariable String deviceId,@PathVariable String channelId, String startTime, String endTime){
|
||||
|
||||
Device device = storager.queryVideoDevice(deviceId);
|
||||
String ssrc = cmder.playStreamCmd(device, channelId);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("设备预览 API调用,deviceId:%s ,channelId:%s",deviceId, channelId));
|
||||
logger.debug("设备预览 API调用,ssrc:"+ssrc+",ZLMedia streamId:"+Integer.toHexString(Integer.parseInt(ssrc)));
|
||||
}
|
||||
|
||||
if(ssrc!=null) {
|
||||
return new ResponseEntity<String>(ssrc,HttpStatus.OK);
|
||||
} else {
|
||||
logger.warn("设备预览API调用失败!");
|
||||
return new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,8 +5,8 @@ import org.slf4j.LoggerFactory;
|
|||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
|
@ -37,7 +37,7 @@ public class PtzController {
|
|||
* @param zoomSpeed
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/ptz/{deviceId}_{channelId}")
|
||||
@PostMapping("/ptz/{deviceId}/{channelId}")
|
||||
public ResponseEntity<String> ptz(@PathVariable String deviceId,@PathVariable String channelId,int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed){
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
|
|
|
@ -31,17 +31,18 @@ public class RecordController {
|
|||
@Autowired
|
||||
private DeferredResultHolder resultHolder;
|
||||
|
||||
@GetMapping("/recordinfo/{deviceId}")
|
||||
public DeferredResult<ResponseEntity<RecordInfo>> recordinfo(@PathVariable String deviceId, String startTime, String endTime){
|
||||
@GetMapping("/record/{deviceId}")
|
||||
public DeferredResult<ResponseEntity<RecordInfo>> recordinfo(@PathVariable String deviceId, String channelId, String startTime, String endTime){
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("录像信息 API调用,deviceId:%s ,startTime:%s, startTime:%s",deviceId, startTime, endTime));
|
||||
}
|
||||
|
||||
Device device = storager.queryVideoDevice(deviceId);
|
||||
cmder.recordInfoQuery(device, startTime, endTime);
|
||||
cmder.recordInfoQuery(device, channelId, startTime, endTime);
|
||||
DeferredResult<ResponseEntity<RecordInfo>> result = new DeferredResult<ResponseEntity<RecordInfo>>();
|
||||
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_CATALOG+deviceId, result);
|
||||
// 录像查询以channelId作为deviceId查询
|
||||
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_RECORDINFO+channelId, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
spring:
|
||||
application:
|
||||
name: wvp
|
||||
# 数据存储方式,暂只支持redis,后续支持jdbc
|
||||
name: iot-vmp-vmanager
|
||||
# 影子数据存储方式,支持redis、jdbc
|
||||
database: redis
|
||||
# 通信方式,支持kafka、http
|
||||
communicate: http
|
||||
redis:
|
||||
# Redis服务器IP
|
||||
#host: 10.24.20.63
|
||||
host: 127.0.0.1
|
||||
#端口号
|
||||
port: 6379
|
||||
|
@ -13,24 +16,49 @@ spring:
|
|||
password:
|
||||
#超时时间
|
||||
timeout: 10000
|
||||
# 可用连接实例的最大数目,默认值为8
|
||||
maxTotal: 512
|
||||
#控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8
|
||||
maxIdle: 100
|
||||
#最小空闲连接数
|
||||
minIdle: 50
|
||||
#获取连接时的最大等待毫秒数,小于零:阻塞不确定的时间,默认-1
|
||||
maxWaitMillis: 10000
|
||||
#每次释放连接的最大数目
|
||||
numTestsPerEvictionRun: 100
|
||||
#释放连接的扫描间隔(毫秒)
|
||||
timeBetweenEvictionRunsMillis: 3000
|
||||
#连接最小空闲时间
|
||||
minEvictableIdleTimeMillis: 1800000
|
||||
#连接空闲多久后释放,当空闲时间>该值且空闲连接>最大空闲连接数时直接释放
|
||||
softMinEvictableIdleTimeMillis: 10000
|
||||
#在获取连接的时候检查有效性,默认false
|
||||
testOnBorrow: true
|
||||
#在空闲时检查有效性,默认false
|
||||
testWhileIdle: true
|
||||
#在归还给pool时,是否提前进行validate操作
|
||||
testOnReturn: true
|
||||
#连接耗尽时是否阻塞,false报异常,ture阻塞直到超时,默认true
|
||||
blockWhenExhausted: false
|
||||
datasource:
|
||||
name: wcp
|
||||
url: jdbc:mysql://127.0.0.1:3306/wcp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true
|
||||
name: eiot
|
||||
url: jdbc:mysql://10.24.20.63:3306/eiot?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true
|
||||
username: root
|
||||
password: 123456
|
||||
password: Ptjsinspur19.?
|
||||
type: com.alibaba.druid.pool.DruidDataSource
|
||||
driver-class-name: com.mysql.jdbc.Driver
|
||||
server:
|
||||
port: 8080
|
||||
sip:
|
||||
# 本地服务地址
|
||||
ip: 192.168.0.3
|
||||
server_id: 34020000002000000001
|
||||
ip: 10.200.64.63
|
||||
port: 5060
|
||||
domain: 34020000
|
||||
# 暂时使用统一密码,后续改为一机一密
|
||||
# 根据国标6.1.2中规定,domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码(由省级、市级、区级、基层编号组成,参照GB/T 2260-2007)
|
||||
# 后两位为行业编码,定义参照附录D.3
|
||||
# 3701020049标识山东济南历下区 信息行业接入
|
||||
domain: 3701020049
|
||||
server_id: 37010200492000000001
|
||||
# 默认设备认证密码,后续扩展使用设备单独密码
|
||||
password: admin
|
||||
media:
|
||||
# ZLMediaServer IP
|
||||
ip: 192.168.0.4
|
||||
ip: 10.200.64.88
|
||||
port: 10000
|
Loading…
Reference in New Issue