Commit 3a502b36a8cfcdd455edb22e5771115559873731

Authored by songww
1 parent 2e778e34

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

src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java
... ... @@ -3,7 +3,7 @@ package com.genersoft.iot.vmp.conf;
3 3 import org.springframework.beans.factory.annotation.Value;
4 4 import org.springframework.context.annotation.Configuration;
5 5  
6   -@Configuration
  6 +@Configuration("sipConfig")
7 7 public class SipConfig {
8 8  
9 9 @Value("${sip.ip}")
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java
... ... @@ -42,7 +42,7 @@ public class SipLayer implements SipListener, Runnable {
42 42 private final static Logger logger = LoggerFactory.getLogger(SipLayer.class);
43 43  
44 44 @Autowired
45   - private SipConfig config;
  45 + private SipConfig sipConfig;
46 46  
47 47 private SipProvider tcpSipProvider;
48 48  
... ... @@ -77,7 +77,7 @@ public class SipLayer implements SipListener, Runnable {
77 77  
78 78 Properties properties = new Properties();
79 79 properties.setProperty("javax.sip.STACK_NAME", "GB28181_SIP");
80   - properties.setProperty("javax.sip.IP_ADDRESS", config.getSipIp());
  80 + properties.setProperty("javax.sip.IP_ADDRESS", sipConfig.getSipIp());
81 81 properties.setProperty("gov.nist.javax.sip.LOG_MESSAGE_CONTENT", "false");
82 82 /**
83 83 * sip_server_log.log 和 sip_debug_log.log public static final int TRACE_NONE =
... ... @@ -92,20 +92,20 @@ public class SipLayer implements SipListener, Runnable {
92 92 startTcpListener();
93 93 startUdpListener();
94 94 } catch (Exception e) {
95   - logger.error("Sip Server 启动失败! port {" + config.getSipPort() + "}");
  95 + logger.error("Sip Server 启动失败! port {" + sipConfig.getSipPort() + "}");
96 96 e.printStackTrace();
97 97 }
98   - logger.info("Sip Server 启动成功 port {" + config.getSipPort() + "}");
  98 + logger.info("Sip Server 启动成功 port {" + sipConfig.getSipPort() + "}");
99 99 }
100 100  
101 101 private void startTcpListener() throws Exception {
102   - ListeningPoint tcpListeningPoint = sipStack.createListeningPoint(config.getSipIp(), config.getSipPort(), "TCP");
  102 + ListeningPoint tcpListeningPoint = sipStack.createListeningPoint(sipConfig.getSipIp(), sipConfig.getSipPort(), "TCP");
103 103 tcpSipProvider = sipStack.createSipProvider(tcpListeningPoint);
104 104 tcpSipProvider.addSipListener(this);
105 105 }
106 106  
107 107 private void startUdpListener() throws Exception {
108   - ListeningPoint udpListeningPoint = sipStack.createListeningPoint(config.getSipIp(), config.getSipPort(), "UDP");
  108 + ListeningPoint udpListeningPoint = sipStack.createListeningPoint(sipConfig.getSipIp(), sipConfig.getSipPort(), "UDP");
109 109 udpSipProvider = sipStack.createSipProvider(udpListeningPoint);
110 110 udpSipProvider.addSipListener(this);
111 111 }
... ... @@ -126,7 +126,7 @@ public class SipLayer implements SipListener, Runnable {
126 126 int status = response.getStatusCode();
127 127 if ((status >= 200) && (status < 300)) { // Success!
128 128 ISIPResponseProcessor processor = processorFactory.createResponseProcessor(evt);
129   - processor.process(evt, this, config);
  129 + processor.process(evt, this, sipConfig);
130 130 } else {
131 131 logger.warn("接收到失败的response响应!status:" + status + ",message:" + response.getContent().toString());
132 132 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordItem.java
... ... @@ -23,7 +23,7 @@ public class RecordItem {
23 23  
24 24 private String type;
25 25  
26   - private String recordId;
  26 + private String recorderId;
27 27  
28 28 public String getDeviceId() {
29 29 return deviceId;
... ... @@ -81,12 +81,12 @@ public class RecordItem {
81 81 this.type = type;
82 82 }
83 83  
84   - public String getRecordId() {
85   - return recordId;
  84 + public String getRecorderId() {
  85 + return recorderId;
86 86 }
87 87  
88   - public void setRecordId(String recordId) {
89   - this.recordId = recordId;
  88 + public void setRecordId(String recorderId) {
  89 + this.recorderId = recorderId;
90 90 }
91 91  
92 92 public String getEndTime() {
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
... ... @@ -72,6 +72,16 @@ public interface ISIPCommander {
72 72 public String playStreamCmd(Device device,String channelId);
73 73  
74 74 /**
  75 + * 请求回放视频流
  76 + *
  77 + * @param device 视频设备
  78 + * @param channelId 预览通道
  79 + * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
  80 + * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
  81 + */
  82 + public String playbackStreamCmd(Device device,String channelId, String recordId, String startTime, String endTime);
  83 +
  84 + /**
75 85 * 语音广播
76 86 *
77 87 * @param device 视频设备
... ... @@ -153,7 +163,7 @@ public interface ISIPCommander {
153 163 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
154 164 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
155 165 */
156   - public boolean recordInfoQuery(Device device, String startTime, String endTime);
  166 + public boolean recordInfoQuery(Device device, String channelId, String startTime, String endTime);
157 167  
158 168 /**
159 169 * 查询报警信息
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java
... ... @@ -35,7 +35,7 @@ public class SIPRequestHeaderProvider {
35 35 private SipLayer layer;
36 36  
37 37 @Autowired
38   - private SipConfig config;
  38 + private SipConfig sipConfig;
39 39  
40 40 public Request createMessageRequest(Device device, String content, String viaTag, String fromTag, String toTag) throws ParseException, InvalidArgumentException {
41 41 Request request = null;
... ... @@ -44,12 +44,12 @@ public class SIPRequestHeaderProvider {
44 44 SipURI requestURI = layer.getAddressFactory().createSipURI(device.getDeviceId(), host.getAddress());
45 45 // via
46 46 ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
47   - ViaHeader viaHeader = layer.getHeaderFactory().createViaHeader(config.getSipIp(), config.getSipPort(),
  47 + ViaHeader viaHeader = layer.getHeaderFactory().createViaHeader(sipConfig.getSipIp(), sipConfig.getSipPort(),
48 48 device.getTransport(), viaTag);
49 49 viaHeaders.add(viaHeader);
50 50 // from
51 51 SipURI fromSipURI = layer.getAddressFactory().createSipURI(device.getDeviceId(),
52   - config.getSipIp() + ":" + config.getSipPort());
  52 + sipConfig.getSipIp() + ":" + sipConfig.getSipPort());
53 53 Address fromAddress = layer.getAddressFactory().createAddress(fromSipURI);
54 54 FromHeader fromHeader = layer.getHeaderFactory().createFromHeader(fromAddress, fromTag);
55 55 // to
... ... @@ -78,11 +78,11 @@ public class SIPRequestHeaderProvider {
78 78 SipURI requestLine = layer.getAddressFactory().createSipURI(device.getDeviceId(), host.getAddress());
79 79 //via
80 80 ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
81   - ViaHeader viaHeader = layer.getHeaderFactory().createViaHeader(config.getSipIp(), config.getSipPort(), device.getTransport(), viaTag);
  81 + ViaHeader viaHeader = layer.getHeaderFactory().createViaHeader(sipConfig.getSipIp(), sipConfig.getSipPort(), device.getTransport(), viaTag);
82 82 viaHeader.setRPort();
83 83 viaHeaders.add(viaHeader);
84 84 //from
85   - SipURI fromSipURI = layer.getAddressFactory().createSipURI(device.getDeviceId(),config.getSipIp()+":"+config.getSipPort());
  85 + SipURI fromSipURI = layer.getAddressFactory().createSipURI(device.getDeviceId(),sipConfig.getSipIp()+":"+sipConfig.getSipPort());
86 86 Address fromAddress = layer.getAddressFactory().createAddress(fromSipURI);
87 87 FromHeader fromHeader = layer.getHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack
88 88 //to
... ...
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;
17 17 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
18 18 import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider;
19 19 import com.genersoft.iot.vmp.gb28181.utils.DateUtil;
  20 +import com.genersoft.iot.vmp.gb28181.utils.SsrcUtil;
  21 +
  22 +import tk.mybatis.mapper.util.StringUtil;
20 23  
21 24 /**
22 25 * @Description:设备能力接口,用于定义设备的控制、查询能力
... ... @@ -27,7 +30,7 @@ import com.genersoft.iot.vmp.gb28181.utils.DateUtil;
27 30 public class SIPCommander implements ISIPCommander {
28 31  
29 32 @Autowired
30   - private SipConfig config;
  33 + private SipConfig sipConfig;
31 34  
32 35 @Autowired
33 36 private SIPRequestHeaderProvider headerProvider;
... ... @@ -46,7 +49,7 @@ public class SIPCommander implements ISIPCommander {
46 49 */
47 50 @Override
48 51 public boolean ptzdirectCmd(Device device, String channelId, int leftRight, int upDown) {
49   - return ptzCmd(device, channelId, leftRight, upDown, 0, config.getSpeed(), 0);
  52 + return ptzCmd(device, channelId, leftRight, upDown, 0, sipConfig.getSpeed(), 0);
50 53 }
51 54  
52 55 /**
... ... @@ -72,7 +75,7 @@ public class SIPCommander implements ISIPCommander {
72 75 */
73 76 @Override
74 77 public boolean ptzZoomCmd(Device device, String channelId, int inOut) {
75   - return ptzCmd(device, channelId, 0, 0, inOut, 0, config.getSpeed());
  78 + return ptzCmd(device, channelId, 0, 0, inOut, 0, sipConfig.getSpeed());
76 79 }
77 80  
78 81 /**
... ... @@ -135,23 +138,19 @@ public class SIPCommander implements ISIPCommander {
135 138 public String playStreamCmd(Device device, String channelId) {
136 139 try {
137 140  
138   - //生成ssrc标识数据流 10位数字
139   - String ssrc = "";
140   - Random random = new Random();
141   - // ZLMediaServer最大识别7FFFFFFF即2147483647,所以随机数不能超过这个数
142   - ssrc = String.valueOf(random.nextInt(2147483647));
  141 + String ssrc = SsrcUtil.getPlaySsrc();
143 142 //
144 143 StringBuffer content = new StringBuffer(200);
145 144 content.append("v=0\r\n");
146   - content.append("o="+channelId+" 0 0 IN IP4 "+config.getSipIp()+"\r\n");
  145 + content.append("o="+channelId+" 0 0 IN IP4 "+sipConfig.getSipIp()+"\r\n");
147 146 content.append("s=Play\r\n");
148   - content.append("c=IN IP4 "+config.getMediaIp()+"\r\n");
  147 + content.append("c=IN IP4 "+sipConfig.getMediaIp()+"\r\n");
149 148 content.append("t=0 0\r\n");
150 149 if(device.getTransport().equals("TCP")) {
151   - content.append("m=video "+config.getMediaPort()+" TCP/RTP/AVP 96 98 97\r\n");
  150 + content.append("m=video "+sipConfig.getMediaPort()+" TCP/RTP/AVP 96 98 97\r\n");
152 151 }
153 152 if(device.getTransport().equals("UDP")) {
154   - content.append("m=video "+config.getMediaPort()+" RTP/AVP 96 98 97\r\n");
  153 + content.append("m=video "+sipConfig.getMediaPort()+" RTP/AVP 96 98 97\r\n");
155 154 }
156 155 content.append("a=sendrecv\r\n");
157 156 content.append("a=rtpmap:96 PS/90000\r\n");
... ... @@ -172,6 +171,53 @@ public class SIPCommander implements ISIPCommander {
172 171 return null;
173 172 }
174 173 }
  174 +
  175 + /**
  176 + * 请求回放视频流
  177 + *
  178 + * @param device 视频设备
  179 + * @param channelId 预览通道
  180 + * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
  181 + * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
  182 + */
  183 + @Override
  184 + public String playbackStreamCmd(Device device, String channelId, String recordId, String startTime, String endTime) {
  185 + try {
  186 +
  187 + String ssrc = SsrcUtil.getPlayBackSsrc();
  188 + //
  189 + StringBuffer content = new StringBuffer(200);
  190 + content.append("v=0\r\n");
  191 + content.append("o="+channelId+" 0 0 IN IP4 "+sipConfig.getSipIp()+"\r\n");
  192 + content.append("s=Playback\r\n");
  193 + content.append("u="+recordId+":3\r\n");
  194 + content.append("c=IN IP4 "+sipConfig.getMediaIp()+"\r\n");
  195 + content.append("t="+DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime)+" "+DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime) +"\r\n");
  196 + if(device.getTransport().equals("TCP")) {
  197 + content.append("m=video "+sipConfig.getMediaPort()+" TCP/RTP/AVP 96 98 97\r\n");
  198 + }
  199 + if(device.getTransport().equals("UDP")) {
  200 + content.append("m=video "+sipConfig.getMediaPort()+" RTP/AVP 96 98 97\r\n");
  201 + }
  202 + content.append("a=recvonly\r\n");
  203 + content.append("a=rtpmap:96 PS/90000\r\n");
  204 + content.append("a=rtpmap:98 H264/90000\r\n");
  205 + content.append("a=rtpmap:97 MPEG4/90000\r\n");
  206 + if(device.getTransport().equals("TCP")){
  207 + content.append("a=setup:passive\r\n");
  208 + content.append("a=connection:new\r\n");
  209 + }
  210 + content.append("y="+ssrc+"\r\n");//ssrc
  211 +
  212 + Request request = headerProvider.createInviteRequest(device, content.toString(), null, "live", null);
  213 +
  214 + transmitRequest(device, request);
  215 + return ssrc;
  216 + } catch ( SipException | ParseException | InvalidArgumentException e) {
  217 + e.printStackTrace();
  218 + return null;
  219 + }
  220 + }
175 221  
176 222 /**
177 223 * 语音广播
... ... @@ -323,22 +369,23 @@ public class SIPCommander implements ISIPCommander {
323 369 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
324 370 */
325 371 @Override
326   - public boolean recordInfoQuery(Device device, String startTime, String endTime) {
  372 + public boolean recordInfoQuery(Device device, String channelId, String startTime, String endTime) {
327 373  
328 374 try {
329   - StringBuffer catalogXml = new StringBuffer(200);
330   - catalogXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>");
331   - catalogXml.append("<Query>");
332   - catalogXml.append("<CmdType>RecordInfo</CmdType>");
333   - catalogXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>");
334   - catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>");
335   - catalogXml.append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(startTime) + "</StartTime>");
336   - catalogXml.append("<EndTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(endTime) + "</EndTime>");
  375 + StringBuffer recordInfoXml = new StringBuffer(200);
  376 + recordInfoXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>");
  377 + recordInfoXml.append("<Query>");
  378 + recordInfoXml.append("<CmdType>RecordInfo</CmdType>");
  379 + recordInfoXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>");
  380 + recordInfoXml.append("<DeviceID>" + channelId + "</DeviceID>");
  381 + recordInfoXml.append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(startTime) + "</StartTime>");
  382 + recordInfoXml.append("<EndTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(endTime) + "</EndTime>");
  383 + recordInfoXml.append("<Secrecy>0</Secrecy>");
337 384 // 大华NVR要求必须增加一个值为all的文本元素节点Type
338   - catalogXml.append("<Type>all</Type>");
339   - catalogXml.append("</Query>");
  385 + recordInfoXml.append("<Type>all</Type>");
  386 + recordInfoXml.append("</Query>");
340 387  
341   - Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "ViaRecordInfoBranch", "FromRecordInfoTag", "ToRecordInfoTag");
  388 + Request request = headerProvider.createMessageRequest(device, recordInfoXml.toString(), "ViaRecordInfoBranch", "FromRecordInfoTag", "ToRecordInfoTag");
342 389 transmitRequest(device, request);
343 390 } catch (SipException | ParseException | InvalidArgumentException e) {
344 391 e.printStackTrace();
... ... @@ -398,4 +445,5 @@ public class SIPCommander implements ISIPCommander {
398 445 sipLayer.getUdpSipProvider().sendRequest(request);
399 446 }
400 447 }
  448 +
401 449 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java
... ... @@ -19,6 +19,8 @@ import org.dom4j.Document;
19 19 import org.dom4j.DocumentException;
20 20 import org.dom4j.Element;
21 21 import org.dom4j.io.SAXReader;
  22 +import org.slf4j.Logger;
  23 +import org.slf4j.LoggerFactory;
22 24 import org.springframework.beans.factory.annotation.Autowired;
23 25 import org.springframework.stereotype.Component;
24 26  
... ... @@ -36,6 +38,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.request.ISIPRequestProcessor;
36 38 import com.genersoft.iot.vmp.gb28181.utils.DateUtil;
37 39 import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
38 40 import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
  41 +import com.genersoft.iot.vmp.utils.redis.RedisUtil;
39 42  
40 43 /**
41 44 * @Description:MESSAGE请求处理器
... ... @@ -44,7 +47,9 @@ import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
44 47 */
45 48 @Component
46 49 public class MessageRequestProcessor implements ISIPRequestProcessor {
47   -
  50 +
  51 + private final static Logger logger = LoggerFactory.getLogger(MessageRequestProcessor.class);
  52 +
48 53 private ServerTransaction transaction;
49 54  
50 55 private SipLayer layer;
... ... @@ -59,8 +64,13 @@ public class MessageRequestProcessor implements ISIPRequestProcessor {
59 64 private EventPublisher publisher;
60 65  
61 66 @Autowired
  67 + private RedisUtil redis;
  68 +
  69 + @Autowired
62 70 private DeferredResultHolder deferredResultHolder;
63 71  
  72 + private final static String CACHE_RECORDINFO_KEY = "CACHE_RECORDINFO_";
  73 +
64 74 /**
65 75 * 处理MESSAGE请求
66 76 *
... ... @@ -77,14 +87,19 @@ public class MessageRequestProcessor implements ISIPRequestProcessor {
77 87 Request request = evt.getRequest();
78 88  
79 89 if (new String(request.getRawContent()).contains("<CmdType>Keepalive</CmdType>")) {
  90 + logger.info("接收到KeepAlive消息");
80 91 processMessageKeepAlive(evt);
81 92 } else if (new String(request.getRawContent()).contains("<CmdType>Catalog</CmdType>")) {
  93 + logger.info("接收到Catalog消息");
82 94 processMessageCatalogList(evt);
83 95 } else if (new String(request.getRawContent()).contains("<CmdType>DeviceInfo</CmdType>")) {
  96 + logger.info("接收到DeviceInfo消息");
84 97 processMessageDeviceInfo(evt);
85 98 } else if (new String(request.getRawContent()).contains("<CmdType>Alarm</CmdType>")) {
  99 + logger.info("接收到Alarm消息");
86 100 processMessageAlarm(evt);
87   - } else if (new String(request.getRawContent()).contains("<CmdType>recordInfo</CmdType>")) {
  101 + } else if (new String(request.getRawContent()).contains("<CmdType>RecordInfo</CmdType>")) {
  102 + logger.info("接收到RecordInfo消息");
88 103 processMessageRecordInfo(evt);
89 104 }
90 105  
... ... @@ -245,6 +260,7 @@ public class MessageRequestProcessor implements ISIPRequestProcessor {
245 260  
246 261 /***
247 262 * 收到catalog设备目录列表请求 处理
  263 + * TODO 过期时间暂时写死180秒,后续与DeferredResult超时时间保持一致
248 264 * @param evt
249 265 */
250 266 private void processMessageRecordInfo(RequestEvent evt) {
... ... @@ -256,15 +272,15 @@ public class MessageRequestProcessor implements ISIPRequestProcessor {
256 272 recordInfo.setDeviceId(deviceId);
257 273 recordInfo.setName(XmlUtil.getText(rootElement,"Name"));
258 274 recordInfo.setSumNum(Integer.parseInt(XmlUtil.getText(rootElement,"SumNum")));
  275 + String sn = XmlUtil.getText(rootElement,"SN");
259 276 Element recordListElement = rootElement.element("RecordList");
260 277 if (recordListElement == null) {
261 278 return;
262 279 }
263 280  
264 281 Iterator<Element> recordListIterator = recordListElement.elementIterator();
  282 + List<RecordItem> recordList = new ArrayList<RecordItem>();
265 283 if (recordListIterator != null) {
266   -
267   - List<RecordItem> recordList = new ArrayList<RecordItem>();
268 284 RecordItem record = new RecordItem();
269 285 // 遍历DeviceList
270 286 while (recordListIterator.hasNext()) {
... ... @@ -273,6 +289,7 @@ public class MessageRequestProcessor implements ISIPRequestProcessor {
273 289 if (recordElement == null) {
274 290 continue;
275 291 }
  292 + record = new RecordItem();
276 293 record.setDeviceId(XmlUtil.getText(itemRecord,"DeviceID"));
277 294 record.setName(XmlUtil.getText(itemRecord,"Name"));
278 295 record.setFilePath(XmlUtil.getText(itemRecord,"FilePath"));
... ... @@ -281,13 +298,42 @@ public class MessageRequestProcessor implements ISIPRequestProcessor {
281 298 record.setEndTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(XmlUtil.getText(itemRecord,"EndTime")));
282 299 record.setSecrecy(itemRecord.element("Secrecy") == null? 0:Integer.parseInt(XmlUtil.getText(itemRecord,"Secrecy")));
283 300 record.setType(XmlUtil.getText(itemRecord,"Type"));
284   - record.setRecordId(XmlUtil.getText(itemRecord,"RecordID"));
  301 + record.setRecordId(XmlUtil.getText(itemRecord,"RecorderID"));
285 302 recordList.add(record);
286 303 }
287 304 recordInfo.setRecordList(recordList);
288 305 }
289 306  
290   -
  307 + // 存在录像且如果当前录像明细个数小于总条数,说明拆包返回,需要组装,暂不返回
  308 + if (recordInfo.getSumNum() > 0 && recordList.size() > 0 && recordList.size() < recordInfo.getSumNum()) {
  309 + // 为防止连续请求该设备的录像数据,返回数据错乱,特增加sn进行区分
  310 + String cacheKey = CACHE_RECORDINFO_KEY+deviceId+sn;
  311 + // TODO 暂时直接操作redis存储,后续封装专用缓存接口,改为本地内存缓存
  312 + if (redis.hasKey(cacheKey)) {
  313 + List<RecordItem> previousList = (List<RecordItem>) redis.get(cacheKey);
  314 + if (previousList != null && previousList.size() > 0) {
  315 + recordList.addAll(previousList);
  316 + }
  317 + // 本分支表示录像列表被拆包,且加上之前的数据还是不够,保存缓存返回,等待下个包再处理
  318 + if (recordList.size() < recordInfo.getSumNum()) {
  319 + redis.set(cacheKey, recordList, 180);
  320 + return;
  321 + } else {
  322 + // 本分支表示录像被拆包,但加上之前的数据够足够,返回响应
  323 + // 因设备心跳有监听redis过期机制,为提高性能,此处手动删除
  324 + redis.del(cacheKey);
  325 + }
  326 + } else {
  327 + // 本分支有两种可能:1、录像列表被拆包,且是第一个包,直接保存缓存返回,等待下个包再处理
  328 + // 2、之前有包,但超时清空了,那么这次sn批次的响应数据已经不完整,等待过期时间后redis自动清空数据
  329 + redis.set(cacheKey, recordList, 180);
  330 + return;
  331 + }
  332 +
  333 + }
  334 + // 走到这里,有以下可能:1、没有录像信息,第一次收到recordinfo的消息即返回响应数据,无redis操作
  335 + // 2、有录像数据,且第一次即收到完整数据,返回响应数据,无redis操作
  336 + // 3、有录像数据,在超时时间内收到多次包组装后数量足够,返回数据
291 337 RequestMessage msg = new RequestMessage();
292 338 msg.setDeviceId(deviceId);
293 339 msg.setType(DeferredResultHolder.CALLBACK_CMD_RECORDINFO);
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/RegisterRequestProcessor.java
... ... @@ -45,7 +45,7 @@ import gov.nist.javax.sip.header.Expires;
45 45 public class RegisterRequestProcessor implements ISIPRequestProcessor {
46 46  
47 47 @Autowired
48   - private SipConfig config;
  48 + private SipConfig sipConfig;
49 49  
50 50 @Autowired
51 51 private RegisterLogicHandler handler;
... ... @@ -77,7 +77,7 @@ public class RegisterRequestProcessor implements ISIPRequestProcessor {
77 77 // 校验密码是否正确
78 78 if (authorhead != null) {
79 79 passwordCorrect = new DigestServerAuthenticationHelper().doAuthenticatePlainTextPassword(request,
80   - config.getSipPassword());
  80 + sipConfig.getSipPassword());
81 81 }
82 82  
83 83 // 未携带授权头或者密码错误 回复401
... ... @@ -89,7 +89,7 @@ public class RegisterRequestProcessor implements ISIPRequestProcessor {
89 89 System.out.println("密码错误 回复401");
90 90 }
91 91 response = layer.getMessageFactory().createResponse(Response.UNAUTHORIZED, request);
92   - new DigestServerAuthenticationHelper().generateChallenge(layer.getHeaderFactory(), response, config.getSipDomain());
  92 + new DigestServerAuthenticationHelper().generateChallenge(layer.getHeaderFactory(), response, sipConfig.getSipDomain());
93 93 }
94 94 // 携带授权头并且密码正确
95 95 else if (passwordCorrect) {
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/utils/DateUtil.java
... ... @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.utils;
2 2  
3 3 import java.text.ParseException;
4 4 import java.text.SimpleDateFormat;
  5 +import java.util.Date;
5 6 import java.util.Locale;
6 7  
7 8 /**
... ... @@ -11,7 +12,8 @@ import java.util.Locale;
11 12 */
12 13 public class DateUtil {
13 14  
14   - private static final String yyyy_MM_dd_T_HH_mm_ss_SSSXXX = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
  15 + //private static final String yyyy_MM_dd_T_HH_mm_ss_SSSXXX = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
  16 + private static final String yyyy_MM_dd_T_HH_mm_ss_SSSXXX = "yyyy-MM-dd'T'HH:mm:ss";
15 17 private static final String yyyy_MM_dd_HH_mm_ss = "yyyy-MM-dd HH:mm:ss";
16 18  
17 19 public static String yyyy_MM_dd_HH_mm_ssToISO8601(String formatTime) {
... ... @@ -37,4 +39,19 @@ public class DateUtil {
37 39 }
38 40 return "";
39 41 }
  42 +
  43 + public static long yyyy_MM_dd_HH_mm_ssToTimestamp(String formatTime) {
  44 + SimpleDateFormat format=new SimpleDateFormat(yyyy_MM_dd_HH_mm_ss);
  45 + //设置要读取的时间字符串格式
  46 + Date date;
  47 + try {
  48 + date = format.parse(formatTime);
  49 + Long timestamp=date.getTime();
  50 + //转换为Date类
  51 + return timestamp;
  52 + } catch (ParseException e) {
  53 + e.printStackTrace();
  54 + }
  55 + return 0;
  56 + }
40 57 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/utils/SsrcUtil.java 0 → 100644
  1 +package com.genersoft.iot.vmp.gb28181.utils;
  2 +
  3 +import java.util.ArrayList;
  4 +import java.util.List;
  5 +import java.util.Random;
  6 +
  7 +import com.genersoft.iot.vmp.conf.SipConfig;
  8 +import com.genersoft.iot.vmp.utils.SpringBeanFactory;
  9 +
  10 +/**
  11 + * @Description:SIP信令中的SSRC工具类。SSRC值由10位十进制整数组成的字符串,第一位为0代表实况,为1则代表回放;第二位至第六位由监控域ID的第4位到第8位组成;最后4位为不重复的4个整数
  12 + * @author: songww
  13 + * @date: 2020年5月10日 上午11:57:57
  14 + */
  15 +public class SsrcUtil {
  16 +
  17 + private static String ssrcPrefix;
  18 +
  19 + private static List<String> isUsed;
  20 +
  21 + private static List<String> notUsed;
  22 +
  23 + private static void init() {
  24 + SipConfig sipConfig = (SipConfig) SpringBeanFactory.getBean("sipConfig");
  25 + ssrcPrefix = sipConfig.getSipDomain().substring(4, 9);
  26 + isUsed = new ArrayList<String>();
  27 + notUsed = new ArrayList<String>();
  28 + for (int i = 1; i < 10000; i++) {
  29 + if (i < 10) {
  30 + notUsed.add("000" + i);
  31 + } else if (i < 100) {
  32 + notUsed.add("00" + i);
  33 + } else if (i < 1000) {
  34 + notUsed.add("0" + i);
  35 + } else {
  36 + notUsed.add(String.valueOf(i));
  37 + }
  38 + }
  39 + }
  40 +
  41 + /**
  42 + * 获取视频预览的SSRC值,第一位固定为0
  43 + *
  44 + */
  45 + public static String getPlaySsrc() {
  46 + return "0" + getSsrcPrefix() + getSN();
  47 + }
  48 +
  49 + /**
  50 + * 获取录像回放的SSRC值,第一位固定为1
  51 + *
  52 + */
  53 + public static String getPlayBackSsrc() {
  54 + return "1" + getSsrcPrefix() + getSN();
  55 + }
  56 +
  57 + /**
  58 + * 释放ssrc,主要用完的ssrc一定要释放,否则会耗尽
  59 + *
  60 + */
  61 + public static void releaseSsrc(String ssrc) {
  62 + String sn = ssrc.substring(6);
  63 + isUsed.remove(sn);
  64 + notUsed.add(sn);
  65 + }
  66 +
  67 + /**
  68 + * 获取后四位数SN,随机数
  69 + *
  70 + */
  71 + private static String getSN() {
  72 + String sn = null;
  73 + if (notUsed.size() == 0) {
  74 + throw new RuntimeException("ssrc已经用完");
  75 + } else if (notUsed.size() == 1) {
  76 + sn = notUsed.get(0);
  77 + } else {
  78 + sn = notUsed.get(new Random().nextInt(notUsed.size() - 1));
  79 + }
  80 + notUsed.remove(0);
  81 + isUsed.add(sn);
  82 + return sn;
  83 + }
  84 +
  85 + private static String getSsrcPrefix() {
  86 + if (ssrcPrefix == null) {
  87 + init();
  88 + }
  89 + return ssrcPrefix;
  90 + }
  91 +}
... ...
src/main/java/com/genersoft/iot/vmp/utils/SpringBeanFactory.java 0 → 100644
  1 +package com.genersoft.iot.vmp.utils;
  2 +
  3 +import org.springframework.beans.BeansException;
  4 +import org.springframework.context.ApplicationContext;
  5 +import org.springframework.context.ApplicationContextAware;
  6 +import org.springframework.stereotype.Component;
  7 +
  8 +/**
  9 + * @Description:spring bean获取工厂,获取spring中的已初始化的bean
  10 + * @author: songww
  11 + * @date: 2019年6月25日 下午4:51:52
  12 + *
  13 + */
  14 +@Component
  15 +public class SpringBeanFactory implements ApplicationContextAware {
  16 +
  17 + // Spring应用上下文环境
  18 + private static ApplicationContext applicationContext;
  19 +
  20 + /**
  21 + * 实现ApplicationContextAware接口的回调方法,设置上下文环境
  22 + */
  23 + @Override
  24 + public void setApplicationContext(ApplicationContext applicationContext)
  25 + throws BeansException {
  26 + SpringBeanFactory.applicationContext = applicationContext;
  27 + }
  28 +
  29 + public static ApplicationContext getApplicationContext() {
  30 + return applicationContext;
  31 + }
  32 +
  33 + /**
  34 + * 获取对象 这里重写了bean方法,起主要作用
  35 + */
  36 + public static Object getBean(String beanId) throws BeansException {
  37 + return applicationContext.getBean(beanId);
  38 + }
  39 +
  40 + /**
  41 + * 获取当前环境
  42 + */
  43 + public static String getActiveProfile() {
  44 + return applicationContext.getEnvironment().getActiveProfiles()[0];
  45 + }
  46 +
  47 +}
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceController.java
1 1 package com.genersoft.iot.vmp.vmanager.device;
2 2  
3 3 import java.util.List;
4   -import java.util.concurrent.ExecutorService;
5   -import java.util.concurrent.Executors;
6 4  
7 5 import org.slf4j.Logger;
8 6 import org.slf4j.LoggerFactory;
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/playback/PlaybackController.java 0 → 100644
  1 +package com.genersoft.iot.vmp.vmanager.playback;
  2 +
  3 +import org.slf4j.Logger;
  4 +import org.slf4j.LoggerFactory;
  5 +import org.springframework.beans.factory.annotation.Autowired;
  6 +import org.springframework.http.HttpStatus;
  7 +import org.springframework.http.ResponseEntity;
  8 +import org.springframework.web.bind.annotation.GetMapping;
  9 +import org.springframework.web.bind.annotation.PathVariable;
  10 +import org.springframework.web.bind.annotation.RequestMapping;
  11 +import org.springframework.web.bind.annotation.RestController;
  12 +
  13 +import com.genersoft.iot.vmp.gb28181.bean.Device;
  14 +import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
  15 +import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
  16 +
  17 +@RestController
  18 +@RequestMapping("/api")
  19 +public class PlaybackController {
  20 +
  21 + private final static Logger logger = LoggerFactory.getLogger(PlaybackController.class);
  22 +
  23 + @Autowired
  24 + private SIPCommander cmder;
  25 +
  26 + @Autowired
  27 + private IVideoManagerStorager storager;
  28 +
  29 + @GetMapping("/playback/{deviceId}/{channelId}")
  30 + public ResponseEntity<String> play(@PathVariable String deviceId,@PathVariable String channelId, String startTime, String endTime){
  31 +
  32 + Device device = storager.queryVideoDevice(deviceId);
  33 + String ssrc = cmder.playStreamCmd(device, channelId);
  34 +
  35 + if (logger.isDebugEnabled()) {
  36 + logger.debug(String.format("设备预览 API调用,deviceId:%s ,channelId:%s",deviceId, channelId));
  37 + logger.debug("设备预览 API调用,ssrc:"+ssrc+",ZLMedia streamId:"+Integer.toHexString(Integer.parseInt(ssrc)));
  38 + }
  39 +
  40 + if(ssrc!=null) {
  41 + return new ResponseEntity<String>(ssrc,HttpStatus.OK);
  42 + } else {
  43 + logger.warn("设备预览API调用失败!");
  44 + return new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);
  45 + }
  46 + }
  47 +}
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/ptz/PtzController.java
... ... @@ -5,8 +5,8 @@ import org.slf4j.LoggerFactory;
5 5 import org.springframework.beans.factory.annotation.Autowired;
6 6 import org.springframework.http.HttpStatus;
7 7 import org.springframework.http.ResponseEntity;
8   -import org.springframework.web.bind.annotation.GetMapping;
9 8 import org.springframework.web.bind.annotation.PathVariable;
  9 +import org.springframework.web.bind.annotation.PostMapping;
10 10 import org.springframework.web.bind.annotation.RequestMapping;
11 11 import org.springframework.web.bind.annotation.RestController;
12 12  
... ... @@ -37,7 +37,7 @@ public class PtzController {
37 37 * @param zoomSpeed
38 38 * @return
39 39 */
40   - @GetMapping("/ptz/{deviceId}_{channelId}")
  40 + @PostMapping("/ptz/{deviceId}/{channelId}")
41 41 public ResponseEntity<String> ptz(@PathVariable String deviceId,@PathVariable String channelId,int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed){
42 42  
43 43 if (logger.isDebugEnabled()) {
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/record/RecordController.java
... ... @@ -31,17 +31,18 @@ public class RecordController {
31 31 @Autowired
32 32 private DeferredResultHolder resultHolder;
33 33  
34   - @GetMapping("/recordinfo/{deviceId}")
35   - public DeferredResult<ResponseEntity<RecordInfo>> recordinfo(@PathVariable String deviceId, String startTime, String endTime){
  34 + @GetMapping("/record/{deviceId}")
  35 + public DeferredResult<ResponseEntity<RecordInfo>> recordinfo(@PathVariable String deviceId, String channelId, String startTime, String endTime){
36 36  
37 37 if (logger.isDebugEnabled()) {
38 38 logger.debug(String.format("录像信息 API调用,deviceId:%s ,startTime:%s, startTime:%s",deviceId, startTime, endTime));
39 39 }
40 40  
41 41 Device device = storager.queryVideoDevice(deviceId);
42   - cmder.recordInfoQuery(device, startTime, endTime);
  42 + cmder.recordInfoQuery(device, channelId, startTime, endTime);
43 43 DeferredResult<ResponseEntity<RecordInfo>> result = new DeferredResult<ResponseEntity<RecordInfo>>();
44   - resultHolder.put(DeferredResultHolder.CALLBACK_CMD_CATALOG+deviceId, result);
  44 + // 录像查询以channelId作为deviceId查询
  45 + resultHolder.put(DeferredResultHolder.CALLBACK_CMD_RECORDINFO+channelId, result);
45 46 return result;
46 47 }
47 48 }
... ...
src/main/resources/application.yml
1 1 spring:
2 2 application:
3   - name: wvp
4   - # 数据存储方式,暂只支持redis,后续支持jdbc
  3 + name: iot-vmp-vmanager
  4 + # 影子数据存储方式,支持redis、jdbc
5 5 database: redis
  6 + # 通信方式,支持kafka、http
  7 + communicate: http
6 8 redis:
7 9 # Redis服务器IP
  10 + #host: 10.24.20.63
8 11 host: 127.0.0.1
9 12 #端口号
10 13 port: 6379
... ... @@ -13,24 +16,49 @@ spring:
13 16 password:
14 17 #超时时间
15 18 timeout: 10000
  19 + # 可用连接实例的最大数目,默认值为8
  20 + maxTotal: 512
  21 + #控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8
  22 + maxIdle: 100
  23 + #最小空闲连接数
  24 + minIdle: 50
  25 + #获取连接时的最大等待毫秒数,小于零:阻塞不确定的时间,默认-1
  26 + maxWaitMillis: 10000
  27 + #每次释放连接的最大数目
  28 + numTestsPerEvictionRun: 100
  29 + #释放连接的扫描间隔(毫秒)
  30 + timeBetweenEvictionRunsMillis: 3000
  31 + #连接最小空闲时间
  32 + minEvictableIdleTimeMillis: 1800000
  33 + #连接空闲多久后释放,当空闲时间>该值且空闲连接>最大空闲连接数时直接释放
  34 + softMinEvictableIdleTimeMillis: 10000
  35 + #在获取连接的时候检查有效性,默认false
  36 + testOnBorrow: true
  37 + #在空闲时检查有效性,默认false
  38 + testWhileIdle: true
  39 + #在归还给pool时,是否提前进行validate操作
  40 + testOnReturn: true
  41 + #连接耗尽时是否阻塞,false报异常,ture阻塞直到超时,默认true
  42 + blockWhenExhausted: false
16 43 datasource:
17   - name: wcp
18   - url: jdbc:mysql://127.0.0.1:3306/wcp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true
  44 + name: eiot
  45 + url: jdbc:mysql://10.24.20.63:3306/eiot?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true
19 46 username: root
20   - password: 123456
  47 + password: Ptjsinspur19.?
21 48 type: com.alibaba.druid.pool.DruidDataSource
22 49 driver-class-name: com.mysql.jdbc.Driver
23 50 server:
24 51 port: 8080
25 52 sip:
26   - # 本地服务地址
27   - ip: 192.168.0.3
28   - server_id: 34020000002000000001
  53 + ip: 10.200.64.63
29 54 port: 5060
30   - domain: 34020000
31   - # 暂时使用统一密码,后续改为一机一密
  55 + # 根据国标6.1.2中规定,domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码(由省级、市级、区级、基层编号组成,参照GB/T 2260-2007)
  56 + # 后两位为行业编码,定义参照附录D.3
  57 + # 3701020049标识山东济南历下区 信息行业接入
  58 + domain: 3701020049
  59 + server_id: 37010200492000000001
  60 + # 默认设备认证密码,后续扩展使用设备单独密码
32 61 password: admin
33 62 media:
34   - # ZLMediaServer IP
35   - ip: 192.168.0.4
  63 + ip: 10.200.64.88
36 64 port: 10000
37 65 \ No newline at end of file
... ...