Browse Source

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

pull/1/head
songww 5 years ago
parent
commit
3a502b36a8
  1. 2
      src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java
  2. 14
      src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java
  3. 10
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordItem.java
  4. 12
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
  5. 10
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java
  6. 96
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
  7. 54
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java
  8. 6
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/RegisterRequestProcessor.java
  9. 19
      src/main/java/com/genersoft/iot/vmp/gb28181/utils/DateUtil.java
  10. 91
      src/main/java/com/genersoft/iot/vmp/gb28181/utils/SsrcUtil.java
  11. 47
      src/main/java/com/genersoft/iot/vmp/utils/SpringBeanFactory.java
  12. 2
      src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceController.java
  13. 47
      src/main/java/com/genersoft/iot/vmp/vmanager/playback/PlaybackController.java
  14. 4
      src/main/java/com/genersoft/iot/vmp/vmanager/ptz/PtzController.java
  15. 9
      src/main/java/com/genersoft/iot/vmp/vmanager/record/RecordController.java
  16. 52
      src/main/resources/application.yml

2
src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java

@ -3,7 +3,7 @@ package com.genersoft.iot.vmp.conf;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@Configuration @Configuration("sipConfig")
public class SipConfig { public class SipConfig {
@Value("${sip.ip}") @Value("${sip.ip}")

14
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java

@ -42,7 +42,7 @@ public class SipLayer implements SipListener, Runnable {
private final static Logger logger = LoggerFactory.getLogger(SipLayer.class); private final static Logger logger = LoggerFactory.getLogger(SipLayer.class);
@Autowired @Autowired
private SipConfig config; private SipConfig sipConfig;
private SipProvider tcpSipProvider; private SipProvider tcpSipProvider;
@ -77,7 +77,7 @@ public class SipLayer implements SipListener, Runnable {
Properties properties = new Properties(); Properties properties = new Properties();
properties.setProperty("javax.sip.STACK_NAME", "GB28181_SIP"); 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"); properties.setProperty("gov.nist.javax.sip.LOG_MESSAGE_CONTENT", "false");
/** /**
* sip_server_log.log sip_debug_log.log public static final int TRACE_NONE = * 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(); startTcpListener();
startUdpListener(); startUdpListener();
} catch (Exception e) { } catch (Exception e) {
logger.error("Sip Server 启动失败! port {" + config.getSipPort() + "}"); logger.error("Sip Server 启动失败! port {" + sipConfig.getSipPort() + "}");
e.printStackTrace(); e.printStackTrace();
} }
logger.info("Sip Server 启动成功 port {" + config.getSipPort() + "}"); logger.info("Sip Server 启动成功 port {" + sipConfig.getSipPort() + "}");
} }
private void startTcpListener() throws Exception { 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 = sipStack.createSipProvider(tcpListeningPoint);
tcpSipProvider.addSipListener(this); tcpSipProvider.addSipListener(this);
} }
private void startUdpListener() throws Exception { 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 = sipStack.createSipProvider(udpListeningPoint);
udpSipProvider.addSipListener(this); udpSipProvider.addSipListener(this);
} }
@ -126,7 +126,7 @@ public class SipLayer implements SipListener, Runnable {
int status = response.getStatusCode(); int status = response.getStatusCode();
if ((status >= 200) && (status < 300)) { // Success! if ((status >= 200) && (status < 300)) { // Success!
ISIPResponseProcessor processor = processorFactory.createResponseProcessor(evt); ISIPResponseProcessor processor = processorFactory.createResponseProcessor(evt);
processor.process(evt, this, config); processor.process(evt, this, sipConfig);
} else { } else {
logger.warn("接收到失败的response响应!status:" + status + ",message:" + response.getContent().toString()); logger.warn("接收到失败的response响应!status:" + status + ",message:" + response.getContent().toString());
} }

10
src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordItem.java

@ -23,7 +23,7 @@ public class RecordItem {
private String type; private String type;
private String recordId; private String recorderId;
public String getDeviceId() { public String getDeviceId() {
return deviceId; return deviceId;
@ -81,12 +81,12 @@ public class RecordItem {
this.type = type; this.type = type;
} }
public String getRecordId() { public String getRecorderId() {
return recordId; return recorderId;
} }
public void setRecordId(String recordId) { public void setRecordId(String recorderId) {
this.recordId = recordId; this.recorderId = recorderId;
} }
public String getEndTime() { public String getEndTime() {

12
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java

@ -71,6 +71,16 @@ public interface ISIPCommander {
*/ */
public String playStreamCmd(Device device,String channelId); 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 startTime 开始时间,格式要求yyyy-MM-dd HH:mm:ss
* @param endTime 结束时间,格式要求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);
/** /**
* 查询报警信息 * 查询报警信息

10
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java

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

96
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java

@ -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.ISIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider; import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider;
import com.genersoft.iot.vmp.gb28181.utils.DateUtil; import com.genersoft.iot.vmp.gb28181.utils.DateUtil;
import com.genersoft.iot.vmp.gb28181.utils.SsrcUtil;
import tk.mybatis.mapper.util.StringUtil;
/** /**
* @Description:设备能力接口用于定义设备的控制查询能力 * @Description:设备能力接口用于定义设备的控制查询能力
@ -27,7 +30,7 @@ import com.genersoft.iot.vmp.gb28181.utils.DateUtil;
public class SIPCommander implements ISIPCommander { public class SIPCommander implements ISIPCommander {
@Autowired @Autowired
private SipConfig config; private SipConfig sipConfig;
@Autowired @Autowired
private SIPRequestHeaderProvider headerProvider; private SIPRequestHeaderProvider headerProvider;
@ -46,7 +49,7 @@ public class SIPCommander implements ISIPCommander {
*/ */
@Override @Override
public boolean ptzdirectCmd(Device device, String channelId, int leftRight, int upDown) { 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 @Override
public boolean ptzZoomCmd(Device device, String channelId, int inOut) { 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) { public String playStreamCmd(Device device, String channelId) {
try { try {
//生成ssrc标识数据流 10位数字 String ssrc = SsrcUtil.getPlaySsrc();
String ssrc = "";
Random random = new Random();
// ZLMediaServer最大识别7FFFFFFF即2147483647,所以随机数不能超过这个数
ssrc = String.valueOf(random.nextInt(2147483647));
// //
StringBuffer content = new StringBuffer(200); StringBuffer content = new StringBuffer(200);
content.append("v=0\r\n"); 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("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"); content.append("t=0 0\r\n");
if(device.getTransport().equals("TCP")) { 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")) { 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=sendrecv\r\n");
content.append("a=rtpmap:96 PS/90000\r\n"); content.append("a=rtpmap:96 PS/90000\r\n");
@ -173,6 +172,53 @@ public class SIPCommander implements ISIPCommander {
} }
} }
/**
* 请求回放视频流
*
* @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 * @param endTime 结束时间,格式要求yyyy-MM-dd HH:mm:ss
*/ */
@Override @Override
public boolean recordInfoQuery(Device device, String startTime, String endTime) { public boolean recordInfoQuery(Device device, String channelId, String startTime, String endTime) {
try { try {
StringBuffer catalogXml = new StringBuffer(200); StringBuffer recordInfoXml = new StringBuffer(200);
catalogXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>"); recordInfoXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>");
catalogXml.append("<Query>"); recordInfoXml.append("<Query>");
catalogXml.append("<CmdType>RecordInfo</CmdType>"); recordInfoXml.append("<CmdType>RecordInfo</CmdType>");
catalogXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>"); recordInfoXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>");
catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>"); recordInfoXml.append("<DeviceID>" + channelId + "</DeviceID>");
catalogXml.append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(startTime) + "</StartTime>"); recordInfoXml.append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(startTime) + "</StartTime>");
catalogXml.append("<EndTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(endTime) + "</EndTime>"); recordInfoXml.append("<EndTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(endTime) + "</EndTime>");
recordInfoXml.append("<Secrecy>0</Secrecy>");
// 大华NVR要求必须增加一个值为all的文本元素节点Type // 大华NVR要求必须增加一个值为all的文本元素节点Type
catalogXml.append("<Type>all</Type>"); recordInfoXml.append("<Type>all</Type>");
catalogXml.append("</Query>"); 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); transmitRequest(device, request);
} catch (SipException | ParseException | InvalidArgumentException e) { } catch (SipException | ParseException | InvalidArgumentException e) {
e.printStackTrace(); e.printStackTrace();
@ -398,4 +445,5 @@ public class SIPCommander implements ISIPCommander {
sipLayer.getUdpSipProvider().sendRequest(request); sipLayer.getUdpSipProvider().sendRequest(request);
} }
} }
} }

54
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java

@ -19,6 +19,8 @@ import org.dom4j.Document;
import org.dom4j.DocumentException; import org.dom4j.DocumentException;
import org.dom4j.Element; import org.dom4j.Element;
import org.dom4j.io.SAXReader; import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; 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.DateUtil;
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil; import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager; import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
/** /**
* @Description:MESSAGE请求处理器 * @Description:MESSAGE请求处理器
@ -45,6 +48,8 @@ import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
@Component @Component
public class MessageRequestProcessor implements ISIPRequestProcessor { public class MessageRequestProcessor implements ISIPRequestProcessor {
private final static Logger logger = LoggerFactory.getLogger(MessageRequestProcessor.class);
private ServerTransaction transaction; private ServerTransaction transaction;
private SipLayer layer; private SipLayer layer;
@ -58,9 +63,14 @@ public class MessageRequestProcessor implements ISIPRequestProcessor {
@Autowired @Autowired
private EventPublisher publisher; private EventPublisher publisher;
@Autowired
private RedisUtil redis;
@Autowired @Autowired
private DeferredResultHolder deferredResultHolder; private DeferredResultHolder deferredResultHolder;
private final static String CACHE_RECORDINFO_KEY = "CACHE_RECORDINFO_";
/** /**
* 处理MESSAGE请求 * 处理MESSAGE请求
* *
@ -77,14 +87,19 @@ public class MessageRequestProcessor implements ISIPRequestProcessor {
Request request = evt.getRequest(); Request request = evt.getRequest();
if (new String(request.getRawContent()).contains("<CmdType>Keepalive</CmdType>")) { if (new String(request.getRawContent()).contains("<CmdType>Keepalive</CmdType>")) {
logger.info("接收到KeepAlive消息");
processMessageKeepAlive(evt); processMessageKeepAlive(evt);
} else if (new String(request.getRawContent()).contains("<CmdType>Catalog</CmdType>")) { } else if (new String(request.getRawContent()).contains("<CmdType>Catalog</CmdType>")) {
logger.info("接收到Catalog消息");
processMessageCatalogList(evt); processMessageCatalogList(evt);
} else if (new String(request.getRawContent()).contains("<CmdType>DeviceInfo</CmdType>")) { } else if (new String(request.getRawContent()).contains("<CmdType>DeviceInfo</CmdType>")) {
logger.info("接收到DeviceInfo消息");
processMessageDeviceInfo(evt); processMessageDeviceInfo(evt);
} else if (new String(request.getRawContent()).contains("<CmdType>Alarm</CmdType>")) { } else if (new String(request.getRawContent()).contains("<CmdType>Alarm</CmdType>")) {
logger.info("接收到Alarm消息");
processMessageAlarm(evt); 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); processMessageRecordInfo(evt);
} }
@ -245,6 +260,7 @@ public class MessageRequestProcessor implements ISIPRequestProcessor {
/*** /***
* 收到catalog设备目录列表请求 处理 * 收到catalog设备目录列表请求 处理
* TODO 过期时间暂时写死180秒后续与DeferredResult超时时间保持一致
* @param evt * @param evt
*/ */
private void processMessageRecordInfo(RequestEvent evt) { private void processMessageRecordInfo(RequestEvent evt) {
@ -256,15 +272,15 @@ public class MessageRequestProcessor implements ISIPRequestProcessor {
recordInfo.setDeviceId(deviceId); recordInfo.setDeviceId(deviceId);
recordInfo.setName(XmlUtil.getText(rootElement,"Name")); recordInfo.setName(XmlUtil.getText(rootElement,"Name"));
recordInfo.setSumNum(Integer.parseInt(XmlUtil.getText(rootElement,"SumNum"))); recordInfo.setSumNum(Integer.parseInt(XmlUtil.getText(rootElement,"SumNum")));
String sn = XmlUtil.getText(rootElement,"SN");
Element recordListElement = rootElement.element("RecordList"); Element recordListElement = rootElement.element("RecordList");
if (recordListElement == null) { if (recordListElement == null) {
return; return;
} }
Iterator<Element> recordListIterator = recordListElement.elementIterator(); Iterator<Element> recordListIterator = recordListElement.elementIterator();
List<RecordItem> recordList = new ArrayList<RecordItem>();
if (recordListIterator != null) { if (recordListIterator != null) {
List<RecordItem> recordList = new ArrayList<RecordItem>();
RecordItem record = new RecordItem(); RecordItem record = new RecordItem();
// 遍历DeviceList // 遍历DeviceList
while (recordListIterator.hasNext()) { while (recordListIterator.hasNext()) {
@ -273,6 +289,7 @@ public class MessageRequestProcessor implements ISIPRequestProcessor {
if (recordElement == null) { if (recordElement == null) {
continue; continue;
} }
record = new RecordItem();
record.setDeviceId(XmlUtil.getText(itemRecord,"DeviceID")); record.setDeviceId(XmlUtil.getText(itemRecord,"DeviceID"));
record.setName(XmlUtil.getText(itemRecord,"Name")); record.setName(XmlUtil.getText(itemRecord,"Name"));
record.setFilePath(XmlUtil.getText(itemRecord,"FilePath")); 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.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.setSecrecy(itemRecord.element("Secrecy") == null? 0:Integer.parseInt(XmlUtil.getText(itemRecord,"Secrecy")));
record.setType(XmlUtil.getText(itemRecord,"Type")); record.setType(XmlUtil.getText(itemRecord,"Type"));
record.setRecordId(XmlUtil.getText(itemRecord,"RecordID")); record.setRecordId(XmlUtil.getText(itemRecord,"RecorderID"));
recordList.add(record); recordList.add(record);
} }
recordInfo.setRecordList(recordList); 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(); RequestMessage msg = new RequestMessage();
msg.setDeviceId(deviceId); msg.setDeviceId(deviceId);
msg.setType(DeferredResultHolder.CALLBACK_CMD_RECORDINFO); msg.setType(DeferredResultHolder.CALLBACK_CMD_RECORDINFO);

6
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/RegisterRequestProcessor.java

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

19
src/main/java/com/genersoft/iot/vmp/gb28181/utils/DateUtil.java

@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.utils;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale; import java.util.Locale;
/** /**
@ -11,7 +12,8 @@ import java.util.Locale;
*/ */
public class DateUtil { 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"; 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) { public static String yyyy_MM_dd_HH_mm_ssToISO8601(String formatTime) {
@ -37,4 +39,19 @@ public class DateUtil {
} }
return ""; 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;
}
} }

91
src/main/java/com/genersoft/iot/vmp/gb28181/utils/SsrcUtil.java

@ -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;
}
}

47
src/main/java/com/genersoft/iot/vmp/utils/SpringBeanFactory.java

@ -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];
}
}

2
src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceController.java

@ -1,8 +1,6 @@
package com.genersoft.iot.vmp.vmanager.device; package com.genersoft.iot.vmp.vmanager.device;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;

47
src/main/java/com/genersoft/iot/vmp/vmanager/playback/PlaybackController.java

@ -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);
}
}
}

4
src/main/java/com/genersoft/iot/vmp/vmanager/ptz/PtzController.java

@ -5,8 +5,8 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; 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.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@ -37,7 +37,7 @@ public class PtzController {
* @param zoomSpeed * @param zoomSpeed
* @return * @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){ public ResponseEntity<String> ptz(@PathVariable String deviceId,@PathVariable String channelId,int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed){
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {

9
src/main/java/com/genersoft/iot/vmp/vmanager/record/RecordController.java

@ -31,17 +31,18 @@ public class RecordController {
@Autowired @Autowired
private DeferredResultHolder resultHolder; private DeferredResultHolder resultHolder;
@GetMapping("/recordinfo/{deviceId}") @GetMapping("/record/{deviceId}")
public DeferredResult<ResponseEntity<RecordInfo>> recordinfo(@PathVariable String deviceId, String startTime, String endTime){ public DeferredResult<ResponseEntity<RecordInfo>> recordinfo(@PathVariable String deviceId, String channelId, String startTime, String endTime){
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("录像信息 API调用,deviceId:%s ,startTime:%s, startTime:%s",deviceId, startTime, endTime)); logger.debug(String.format("录像信息 API调用,deviceId:%s ,startTime:%s, startTime:%s",deviceId, startTime, endTime));
} }
Device device = storager.queryVideoDevice(deviceId); Device device = storager.queryVideoDevice(deviceId);
cmder.recordInfoQuery(device, startTime, endTime); cmder.recordInfoQuery(device, channelId, startTime, endTime);
DeferredResult<ResponseEntity<RecordInfo>> result = new DeferredResult<ResponseEntity<RecordInfo>>(); 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; return result;
} }
} }

52
src/main/resources/application.yml

@ -1,10 +1,13 @@
spring: spring:
application: application:
name: wvp name: iot-vmp-vmanager
# 数据存储方式,暂只支持redis,后续支持jdbc # 影子数据存储方式,支持redis、jdbc
database: redis database: redis
# 通信方式,支持kafka、http
communicate: http
redis: redis:
# Redis服务器IP # Redis服务器IP
#host: 10.24.20.63
host: 127.0.0.1 host: 127.0.0.1
#端口号 #端口号
port: 6379 port: 6379
@ -13,24 +16,49 @@ spring:
password: password:
#超时时间 #超时时间
timeout: 10000 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: datasource:
name: wcp name: eiot
url: jdbc:mysql://127.0.0.1:3306/wcp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true url: jdbc:mysql://10.24.20.63:3306/eiot?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true
username: root username: root
password: 123456 password: Ptjsinspur19.?
type: com.alibaba.druid.pool.DruidDataSource type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver driver-class-name: com.mysql.jdbc.Driver
server: server:
port: 8080 port: 8080
sip: sip:
# 本地服务地址 ip: 10.200.64.63
ip: 192.168.0.3
server_id: 34020000002000000001
port: 5060 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 password: admin
media: media:
# ZLMediaServer IP ip: 10.200.64.88
ip: 192.168.0.4
port: 10000 port: 10000
Loading…
Cancel
Save