Commit 3a502b36a8cfcdd455edb22e5771115559873731
1 parent
2e778e34
完善ssrc符合国标,并完善很多小问题
Showing
16 changed files
with
406 additions
and
73 deletions
src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java
| @@ -3,7 +3,7 @@ package com.genersoft.iot.vmp.conf; | @@ -3,7 +3,7 @@ package com.genersoft.iot.vmp.conf; | ||
| 3 | import org.springframework.beans.factory.annotation.Value; | 3 | import org.springframework.beans.factory.annotation.Value; |
| 4 | import org.springframework.context.annotation.Configuration; | 4 | import org.springframework.context.annotation.Configuration; |
| 5 | 5 | ||
| 6 | -@Configuration | 6 | +@Configuration("sipConfig") |
| 7 | public class SipConfig { | 7 | public class SipConfig { |
| 8 | 8 | ||
| 9 | @Value("${sip.ip}") | 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,7 +42,7 @@ public class SipLayer implements SipListener, Runnable { | ||
| 42 | private final static Logger logger = LoggerFactory.getLogger(SipLayer.class); | 42 | private final static Logger logger = LoggerFactory.getLogger(SipLayer.class); |
| 43 | 43 | ||
| 44 | @Autowired | 44 | @Autowired |
| 45 | - private SipConfig config; | 45 | + private SipConfig sipConfig; |
| 46 | 46 | ||
| 47 | private SipProvider tcpSipProvider; | 47 | private SipProvider tcpSipProvider; |
| 48 | 48 | ||
| @@ -77,7 +77,7 @@ public class SipLayer implements SipListener, Runnable { | @@ -77,7 +77,7 @@ public class SipLayer implements SipListener, Runnable { | ||
| 77 | 77 | ||
| 78 | Properties properties = new Properties(); | 78 | Properties properties = new Properties(); |
| 79 | properties.setProperty("javax.sip.STACK_NAME", "GB28181_SIP"); | 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 | properties.setProperty("gov.nist.javax.sip.LOG_MESSAGE_CONTENT", "false"); | 81 | properties.setProperty("gov.nist.javax.sip.LOG_MESSAGE_CONTENT", "false"); |
| 82 | /** | 82 | /** |
| 83 | * sip_server_log.log 和 sip_debug_log.log public static final int TRACE_NONE = | 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,20 +92,20 @@ public class SipLayer implements SipListener, Runnable { | ||
| 92 | startTcpListener(); | 92 | startTcpListener(); |
| 93 | startUdpListener(); | 93 | startUdpListener(); |
| 94 | } catch (Exception e) { | 94 | } catch (Exception e) { |
| 95 | - logger.error("Sip Server 启动失败! port {" + config.getSipPort() + "}"); | 95 | + logger.error("Sip Server 启动失败! port {" + sipConfig.getSipPort() + "}"); |
| 96 | e.printStackTrace(); | 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 | private void startTcpListener() throws Exception { | 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 | tcpSipProvider = sipStack.createSipProvider(tcpListeningPoint); | 103 | tcpSipProvider = sipStack.createSipProvider(tcpListeningPoint); |
| 104 | tcpSipProvider.addSipListener(this); | 104 | tcpSipProvider.addSipListener(this); |
| 105 | } | 105 | } |
| 106 | 106 | ||
| 107 | private void startUdpListener() throws Exception { | 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 | udpSipProvider = sipStack.createSipProvider(udpListeningPoint); | 109 | udpSipProvider = sipStack.createSipProvider(udpListeningPoint); |
| 110 | udpSipProvider.addSipListener(this); | 110 | udpSipProvider.addSipListener(this); |
| 111 | } | 111 | } |
| @@ -126,7 +126,7 @@ public class SipLayer implements SipListener, Runnable { | @@ -126,7 +126,7 @@ public class SipLayer implements SipListener, Runnable { | ||
| 126 | int status = response.getStatusCode(); | 126 | int status = response.getStatusCode(); |
| 127 | if ((status >= 200) && (status < 300)) { // Success! | 127 | if ((status >= 200) && (status < 300)) { // Success! |
| 128 | ISIPResponseProcessor processor = processorFactory.createResponseProcessor(evt); | 128 | ISIPResponseProcessor processor = processorFactory.createResponseProcessor(evt); |
| 129 | - processor.process(evt, this, config); | 129 | + processor.process(evt, this, sipConfig); |
| 130 | } else { | 130 | } else { |
| 131 | logger.warn("接收到失败的response响应!status:" + status + ",message:" + response.getContent().toString()); | 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,7 +23,7 @@ public class RecordItem { | ||
| 23 | 23 | ||
| 24 | private String type; | 24 | private String type; |
| 25 | 25 | ||
| 26 | - private String recordId; | 26 | + private String recorderId; |
| 27 | 27 | ||
| 28 | public String getDeviceId() { | 28 | public String getDeviceId() { |
| 29 | return deviceId; | 29 | return deviceId; |
| @@ -81,12 +81,12 @@ public class RecordItem { | @@ -81,12 +81,12 @@ public class RecordItem { | ||
| 81 | this.type = type; | 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 | public String getEndTime() { | 92 | public String getEndTime() { |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
| @@ -72,6 +72,16 @@ public interface ISIPCommander { | @@ -72,6 +72,16 @@ public interface ISIPCommander { | ||
| 72 | public String playStreamCmd(Device device,String channelId); | 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 | * @param device 视频设备 | 87 | * @param device 视频设备 |
| @@ -153,7 +163,7 @@ public interface ISIPCommander { | @@ -153,7 +163,7 @@ public interface ISIPCommander { | ||
| 153 | * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss | 163 | * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss |
| 154 | * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss | 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,7 +35,7 @@ public class SIPRequestHeaderProvider { | ||
| 35 | private SipLayer layer; | 35 | private SipLayer layer; |
| 36 | 36 | ||
| 37 | @Autowired | 37 | @Autowired |
| 38 | - private SipConfig config; | 38 | + private SipConfig sipConfig; |
| 39 | 39 | ||
| 40 | public Request createMessageRequest(Device device, String content, String viaTag, String fromTag, String toTag) throws ParseException, InvalidArgumentException { | 40 | public Request createMessageRequest(Device device, String content, String viaTag, String fromTag, String toTag) throws ParseException, InvalidArgumentException { |
| 41 | Request request = null; | 41 | Request request = null; |
| @@ -44,12 +44,12 @@ public class SIPRequestHeaderProvider { | @@ -44,12 +44,12 @@ public class SIPRequestHeaderProvider { | ||
| 44 | SipURI requestURI = layer.getAddressFactory().createSipURI(device.getDeviceId(), host.getAddress()); | 44 | SipURI requestURI = layer.getAddressFactory().createSipURI(device.getDeviceId(), host.getAddress()); |
| 45 | // via | 45 | // via |
| 46 | ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>(); | 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 | device.getTransport(), viaTag); | 48 | device.getTransport(), viaTag); |
| 49 | viaHeaders.add(viaHeader); | 49 | viaHeaders.add(viaHeader); |
| 50 | // from | 50 | // from |
| 51 | SipURI fromSipURI = layer.getAddressFactory().createSipURI(device.getDeviceId(), | 51 | SipURI fromSipURI = layer.getAddressFactory().createSipURI(device.getDeviceId(), |
| 52 | - config.getSipIp() + ":" + config.getSipPort()); | 52 | + sipConfig.getSipIp() + ":" + sipConfig.getSipPort()); |
| 53 | Address fromAddress = layer.getAddressFactory().createAddress(fromSipURI); | 53 | Address fromAddress = layer.getAddressFactory().createAddress(fromSipURI); |
| 54 | FromHeader fromHeader = layer.getHeaderFactory().createFromHeader(fromAddress, fromTag); | 54 | FromHeader fromHeader = layer.getHeaderFactory().createFromHeader(fromAddress, fromTag); |
| 55 | // to | 55 | // to |
| @@ -78,11 +78,11 @@ public class SIPRequestHeaderProvider { | @@ -78,11 +78,11 @@ public class SIPRequestHeaderProvider { | ||
| 78 | SipURI requestLine = layer.getAddressFactory().createSipURI(device.getDeviceId(), host.getAddress()); | 78 | SipURI requestLine = layer.getAddressFactory().createSipURI(device.getDeviceId(), host.getAddress()); |
| 79 | //via | 79 | //via |
| 80 | ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>(); | 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 | viaHeader.setRPort(); | 82 | viaHeader.setRPort(); |
| 83 | viaHeaders.add(viaHeader); | 83 | viaHeaders.add(viaHeader); |
| 84 | //from | 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 | Address fromAddress = layer.getAddressFactory().createAddress(fromSipURI); | 86 | Address fromAddress = layer.getAddressFactory().createAddress(fromSipURI); |
| 87 | FromHeader fromHeader = layer.getHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack | 87 | FromHeader fromHeader = layer.getHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack |
| 88 | //to | 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,6 +17,9 @@ import com.genersoft.iot.vmp.gb28181.bean.Device; | ||
| 17 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; | 17 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; |
| 18 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider; | 18 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider; |
| 19 | import com.genersoft.iot.vmp.gb28181.utils.DateUtil; | 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 | * @Description:设备能力接口,用于定义设备的控制、查询能力 | 25 | * @Description:设备能力接口,用于定义设备的控制、查询能力 |
| @@ -27,7 +30,7 @@ import com.genersoft.iot.vmp.gb28181.utils.DateUtil; | @@ -27,7 +30,7 @@ import com.genersoft.iot.vmp.gb28181.utils.DateUtil; | ||
| 27 | public class SIPCommander implements ISIPCommander { | 30 | public class SIPCommander implements ISIPCommander { |
| 28 | 31 | ||
| 29 | @Autowired | 32 | @Autowired |
| 30 | - private SipConfig config; | 33 | + private SipConfig sipConfig; |
| 31 | 34 | ||
| 32 | @Autowired | 35 | @Autowired |
| 33 | private SIPRequestHeaderProvider headerProvider; | 36 | private SIPRequestHeaderProvider headerProvider; |
| @@ -46,7 +49,7 @@ public class SIPCommander implements ISIPCommander { | @@ -46,7 +49,7 @@ public class SIPCommander implements ISIPCommander { | ||
| 46 | */ | 49 | */ |
| 47 | @Override | 50 | @Override |
| 48 | public boolean ptzdirectCmd(Device device, String channelId, int leftRight, int upDown) { | 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,7 +75,7 @@ public class SIPCommander implements ISIPCommander { | ||
| 72 | */ | 75 | */ |
| 73 | @Override | 76 | @Override |
| 74 | public boolean ptzZoomCmd(Device device, String channelId, int inOut) { | 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,23 +138,19 @@ public class SIPCommander implements ISIPCommander { | ||
| 135 | public String playStreamCmd(Device device, String channelId) { | 138 | public String playStreamCmd(Device device, String channelId) { |
| 136 | try { | 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 | StringBuffer content = new StringBuffer(200); | 143 | StringBuffer content = new StringBuffer(200); |
| 145 | content.append("v=0\r\n"); | 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 | content.append("s=Play\r\n"); | 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 | content.append("t=0 0\r\n"); | 148 | content.append("t=0 0\r\n"); |
| 150 | if(device.getTransport().equals("TCP")) { | 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 | if(device.getTransport().equals("UDP")) { | 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 | content.append("a=sendrecv\r\n"); | 155 | content.append("a=sendrecv\r\n"); |
| 157 | content.append("a=rtpmap:96 PS/90000\r\n"); | 156 | content.append("a=rtpmap:96 PS/90000\r\n"); |
| @@ -172,6 +171,53 @@ public class SIPCommander implements ISIPCommander { | @@ -172,6 +171,53 @@ public class SIPCommander implements ISIPCommander { | ||
| 172 | return null; | 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,22 +369,23 @@ public class SIPCommander implements ISIPCommander { | ||
| 323 | * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss | 369 | * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss |
| 324 | */ | 370 | */ |
| 325 | @Override | 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 | try { | 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 | // 大华NVR要求必须增加一个值为all的文本元素节点Type | 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 | transmitRequest(device, request); | 389 | transmitRequest(device, request); |
| 343 | } catch (SipException | ParseException | InvalidArgumentException e) { | 390 | } catch (SipException | ParseException | InvalidArgumentException e) { |
| 344 | e.printStackTrace(); | 391 | e.printStackTrace(); |
| @@ -398,4 +445,5 @@ public class SIPCommander implements ISIPCommander { | @@ -398,4 +445,5 @@ public class SIPCommander implements ISIPCommander { | ||
| 398 | sipLayer.getUdpSipProvider().sendRequest(request); | 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,6 +19,8 @@ import org.dom4j.Document; | ||
| 19 | import org.dom4j.DocumentException; | 19 | import org.dom4j.DocumentException; |
| 20 | import org.dom4j.Element; | 20 | import org.dom4j.Element; |
| 21 | import org.dom4j.io.SAXReader; | 21 | import org.dom4j.io.SAXReader; |
| 22 | +import org.slf4j.Logger; | ||
| 23 | +import org.slf4j.LoggerFactory; | ||
| 22 | import org.springframework.beans.factory.annotation.Autowired; | 24 | import org.springframework.beans.factory.annotation.Autowired; |
| 23 | import org.springframework.stereotype.Component; | 25 | import org.springframework.stereotype.Component; |
| 24 | 26 | ||
| @@ -36,6 +38,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.request.ISIPRequestProcessor; | @@ -36,6 +38,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.request.ISIPRequestProcessor; | ||
| 36 | import com.genersoft.iot.vmp.gb28181.utils.DateUtil; | 38 | import com.genersoft.iot.vmp.gb28181.utils.DateUtil; |
| 37 | import com.genersoft.iot.vmp.gb28181.utils.XmlUtil; | 39 | import com.genersoft.iot.vmp.gb28181.utils.XmlUtil; |
| 38 | import com.genersoft.iot.vmp.storager.IVideoManagerStorager; | 40 | import com.genersoft.iot.vmp.storager.IVideoManagerStorager; |
| 41 | +import com.genersoft.iot.vmp.utils.redis.RedisUtil; | ||
| 39 | 42 | ||
| 40 | /** | 43 | /** |
| 41 | * @Description:MESSAGE请求处理器 | 44 | * @Description:MESSAGE请求处理器 |
| @@ -44,7 +47,9 @@ import com.genersoft.iot.vmp.storager.IVideoManagerStorager; | @@ -44,7 +47,9 @@ import com.genersoft.iot.vmp.storager.IVideoManagerStorager; | ||
| 44 | */ | 47 | */ |
| 45 | @Component | 48 | @Component |
| 46 | public class MessageRequestProcessor implements ISIPRequestProcessor { | 49 | public class MessageRequestProcessor implements ISIPRequestProcessor { |
| 47 | - | 50 | + |
| 51 | + private final static Logger logger = LoggerFactory.getLogger(MessageRequestProcessor.class); | ||
| 52 | + | ||
| 48 | private ServerTransaction transaction; | 53 | private ServerTransaction transaction; |
| 49 | 54 | ||
| 50 | private SipLayer layer; | 55 | private SipLayer layer; |
| @@ -59,8 +64,13 @@ public class MessageRequestProcessor implements ISIPRequestProcessor { | @@ -59,8 +64,13 @@ public class MessageRequestProcessor implements ISIPRequestProcessor { | ||
| 59 | private EventPublisher publisher; | 64 | private EventPublisher publisher; |
| 60 | 65 | ||
| 61 | @Autowired | 66 | @Autowired |
| 67 | + private RedisUtil redis; | ||
| 68 | + | ||
| 69 | + @Autowired | ||
| 62 | private DeferredResultHolder deferredResultHolder; | 70 | private DeferredResultHolder deferredResultHolder; |
| 63 | 71 | ||
| 72 | + private final static String CACHE_RECORDINFO_KEY = "CACHE_RECORDINFO_"; | ||
| 73 | + | ||
| 64 | /** | 74 | /** |
| 65 | * 处理MESSAGE请求 | 75 | * 处理MESSAGE请求 |
| 66 | * | 76 | * |
| @@ -77,14 +87,19 @@ public class MessageRequestProcessor implements ISIPRequestProcessor { | @@ -77,14 +87,19 @@ public class MessageRequestProcessor implements ISIPRequestProcessor { | ||
| 77 | Request request = evt.getRequest(); | 87 | Request request = evt.getRequest(); |
| 78 | 88 | ||
| 79 | if (new String(request.getRawContent()).contains("<CmdType>Keepalive</CmdType>")) { | 89 | if (new String(request.getRawContent()).contains("<CmdType>Keepalive</CmdType>")) { |
| 90 | + logger.info("接收到KeepAlive消息"); | ||
| 80 | processMessageKeepAlive(evt); | 91 | processMessageKeepAlive(evt); |
| 81 | } else if (new String(request.getRawContent()).contains("<CmdType>Catalog</CmdType>")) { | 92 | } else if (new String(request.getRawContent()).contains("<CmdType>Catalog</CmdType>")) { |
| 93 | + logger.info("接收到Catalog消息"); | ||
| 82 | processMessageCatalogList(evt); | 94 | processMessageCatalogList(evt); |
| 83 | } else if (new String(request.getRawContent()).contains("<CmdType>DeviceInfo</CmdType>")) { | 95 | } else if (new String(request.getRawContent()).contains("<CmdType>DeviceInfo</CmdType>")) { |
| 96 | + logger.info("接收到DeviceInfo消息"); | ||
| 84 | processMessageDeviceInfo(evt); | 97 | processMessageDeviceInfo(evt); |
| 85 | } else if (new String(request.getRawContent()).contains("<CmdType>Alarm</CmdType>")) { | 98 | } else if (new String(request.getRawContent()).contains("<CmdType>Alarm</CmdType>")) { |
| 99 | + logger.info("接收到Alarm消息"); | ||
| 86 | processMessageAlarm(evt); | 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 | processMessageRecordInfo(evt); | 103 | processMessageRecordInfo(evt); |
| 89 | } | 104 | } |
| 90 | 105 | ||
| @@ -245,6 +260,7 @@ public class MessageRequestProcessor implements ISIPRequestProcessor { | @@ -245,6 +260,7 @@ public class MessageRequestProcessor implements ISIPRequestProcessor { | ||
| 245 | 260 | ||
| 246 | /*** | 261 | /*** |
| 247 | * 收到catalog设备目录列表请求 处理 | 262 | * 收到catalog设备目录列表请求 处理 |
| 263 | + * TODO 过期时间暂时写死180秒,后续与DeferredResult超时时间保持一致 | ||
| 248 | * @param evt | 264 | * @param evt |
| 249 | */ | 265 | */ |
| 250 | private void processMessageRecordInfo(RequestEvent evt) { | 266 | private void processMessageRecordInfo(RequestEvent evt) { |
| @@ -256,15 +272,15 @@ public class MessageRequestProcessor implements ISIPRequestProcessor { | @@ -256,15 +272,15 @@ public class MessageRequestProcessor implements ISIPRequestProcessor { | ||
| 256 | recordInfo.setDeviceId(deviceId); | 272 | recordInfo.setDeviceId(deviceId); |
| 257 | recordInfo.setName(XmlUtil.getText(rootElement,"Name")); | 273 | recordInfo.setName(XmlUtil.getText(rootElement,"Name")); |
| 258 | recordInfo.setSumNum(Integer.parseInt(XmlUtil.getText(rootElement,"SumNum"))); | 274 | recordInfo.setSumNum(Integer.parseInt(XmlUtil.getText(rootElement,"SumNum"))); |
| 275 | + String sn = XmlUtil.getText(rootElement,"SN"); | ||
| 259 | Element recordListElement = rootElement.element("RecordList"); | 276 | Element recordListElement = rootElement.element("RecordList"); |
| 260 | if (recordListElement == null) { | 277 | if (recordListElement == null) { |
| 261 | return; | 278 | return; |
| 262 | } | 279 | } |
| 263 | 280 | ||
| 264 | Iterator<Element> recordListIterator = recordListElement.elementIterator(); | 281 | Iterator<Element> recordListIterator = recordListElement.elementIterator(); |
| 282 | + List<RecordItem> recordList = new ArrayList<RecordItem>(); | ||
| 265 | if (recordListIterator != null) { | 283 | if (recordListIterator != null) { |
| 266 | - | ||
| 267 | - List<RecordItem> recordList = new ArrayList<RecordItem>(); | ||
| 268 | RecordItem record = new RecordItem(); | 284 | RecordItem record = new RecordItem(); |
| 269 | // 遍历DeviceList | 285 | // 遍历DeviceList |
| 270 | while (recordListIterator.hasNext()) { | 286 | while (recordListIterator.hasNext()) { |
| @@ -273,6 +289,7 @@ public class MessageRequestProcessor implements ISIPRequestProcessor { | @@ -273,6 +289,7 @@ public class MessageRequestProcessor implements ISIPRequestProcessor { | ||
| 273 | if (recordElement == null) { | 289 | if (recordElement == null) { |
| 274 | continue; | 290 | continue; |
| 275 | } | 291 | } |
| 292 | + record = new RecordItem(); | ||
| 276 | record.setDeviceId(XmlUtil.getText(itemRecord,"DeviceID")); | 293 | record.setDeviceId(XmlUtil.getText(itemRecord,"DeviceID")); |
| 277 | record.setName(XmlUtil.getText(itemRecord,"Name")); | 294 | record.setName(XmlUtil.getText(itemRecord,"Name")); |
| 278 | record.setFilePath(XmlUtil.getText(itemRecord,"FilePath")); | 295 | record.setFilePath(XmlUtil.getText(itemRecord,"FilePath")); |
| @@ -281,13 +298,42 @@ public class MessageRequestProcessor implements ISIPRequestProcessor { | @@ -281,13 +298,42 @@ public class MessageRequestProcessor implements ISIPRequestProcessor { | ||
| 281 | record.setEndTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(XmlUtil.getText(itemRecord,"EndTime"))); | 298 | record.setEndTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(XmlUtil.getText(itemRecord,"EndTime"))); |
| 282 | record.setSecrecy(itemRecord.element("Secrecy") == null? 0:Integer.parseInt(XmlUtil.getText(itemRecord,"Secrecy"))); | 299 | record.setSecrecy(itemRecord.element("Secrecy") == null? 0:Integer.parseInt(XmlUtil.getText(itemRecord,"Secrecy"))); |
| 283 | record.setType(XmlUtil.getText(itemRecord,"Type")); | 300 | record.setType(XmlUtil.getText(itemRecord,"Type")); |
| 284 | - record.setRecordId(XmlUtil.getText(itemRecord,"RecordID")); | 301 | + record.setRecordId(XmlUtil.getText(itemRecord,"RecorderID")); |
| 285 | recordList.add(record); | 302 | recordList.add(record); |
| 286 | } | 303 | } |
| 287 | recordInfo.setRecordList(recordList); | 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 | RequestMessage msg = new RequestMessage(); | 337 | RequestMessage msg = new RequestMessage(); |
| 292 | msg.setDeviceId(deviceId); | 338 | msg.setDeviceId(deviceId); |
| 293 | msg.setType(DeferredResultHolder.CALLBACK_CMD_RECORDINFO); | 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,7 +45,7 @@ import gov.nist.javax.sip.header.Expires; | ||
| 45 | public class RegisterRequestProcessor implements ISIPRequestProcessor { | 45 | public class RegisterRequestProcessor implements ISIPRequestProcessor { |
| 46 | 46 | ||
| 47 | @Autowired | 47 | @Autowired |
| 48 | - private SipConfig config; | 48 | + private SipConfig sipConfig; |
| 49 | 49 | ||
| 50 | @Autowired | 50 | @Autowired |
| 51 | private RegisterLogicHandler handler; | 51 | private RegisterLogicHandler handler; |
| @@ -77,7 +77,7 @@ public class RegisterRequestProcessor implements ISIPRequestProcessor { | @@ -77,7 +77,7 @@ public class RegisterRequestProcessor implements ISIPRequestProcessor { | ||
| 77 | // 校验密码是否正确 | 77 | // 校验密码是否正确 |
| 78 | if (authorhead != null) { | 78 | if (authorhead != null) { |
| 79 | passwordCorrect = new DigestServerAuthenticationHelper().doAuthenticatePlainTextPassword(request, | 79 | passwordCorrect = new DigestServerAuthenticationHelper().doAuthenticatePlainTextPassword(request, |
| 80 | - config.getSipPassword()); | 80 | + sipConfig.getSipPassword()); |
| 81 | } | 81 | } |
| 82 | 82 | ||
| 83 | // 未携带授权头或者密码错误 回复401 | 83 | // 未携带授权头或者密码错误 回复401 |
| @@ -89,7 +89,7 @@ public class RegisterRequestProcessor implements ISIPRequestProcessor { | @@ -89,7 +89,7 @@ public class RegisterRequestProcessor implements ISIPRequestProcessor { | ||
| 89 | System.out.println("密码错误 回复401"); | 89 | System.out.println("密码错误 回复401"); |
| 90 | } | 90 | } |
| 91 | response = layer.getMessageFactory().createResponse(Response.UNAUTHORIZED, request); | 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 | else if (passwordCorrect) { | 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,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.utils; | ||
| 2 | 2 | ||
| 3 | import java.text.ParseException; | 3 | import java.text.ParseException; |
| 4 | import java.text.SimpleDateFormat; | 4 | import java.text.SimpleDateFormat; |
| 5 | +import java.util.Date; | ||
| 5 | import java.util.Locale; | 6 | import java.util.Locale; |
| 6 | 7 | ||
| 7 | /** | 8 | /** |
| @@ -11,7 +12,8 @@ import java.util.Locale; | @@ -11,7 +12,8 @@ import java.util.Locale; | ||
| 11 | */ | 12 | */ |
| 12 | public class DateUtil { | 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 | private static final String yyyy_MM_dd_HH_mm_ss = "yyyy-MM-dd HH:mm:ss"; | 17 | private static final String yyyy_MM_dd_HH_mm_ss = "yyyy-MM-dd HH:mm:ss"; |
| 16 | 18 | ||
| 17 | public static String yyyy_MM_dd_HH_mm_ssToISO8601(String formatTime) { | 19 | public static String yyyy_MM_dd_HH_mm_ssToISO8601(String formatTime) { |
| @@ -37,4 +39,19 @@ public class DateUtil { | @@ -37,4 +39,19 @@ public class DateUtil { | ||
| 37 | } | 39 | } |
| 38 | return ""; | 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 | package com.genersoft.iot.vmp.vmanager.device; | 1 | package com.genersoft.iot.vmp.vmanager.device; |
| 2 | 2 | ||
| 3 | import java.util.List; | 3 | import java.util.List; |
| 4 | -import java.util.concurrent.ExecutorService; | ||
| 5 | -import java.util.concurrent.Executors; | ||
| 6 | 4 | ||
| 7 | import org.slf4j.Logger; | 5 | import org.slf4j.Logger; |
| 8 | import org.slf4j.LoggerFactory; | 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,8 +5,8 @@ import org.slf4j.LoggerFactory; | ||
| 5 | import org.springframework.beans.factory.annotation.Autowired; | 5 | import org.springframework.beans.factory.annotation.Autowired; |
| 6 | import org.springframework.http.HttpStatus; | 6 | import org.springframework.http.HttpStatus; |
| 7 | import org.springframework.http.ResponseEntity; | 7 | import org.springframework.http.ResponseEntity; |
| 8 | -import org.springframework.web.bind.annotation.GetMapping; | ||
| 9 | import org.springframework.web.bind.annotation.PathVariable; | 8 | import org.springframework.web.bind.annotation.PathVariable; |
| 9 | +import org.springframework.web.bind.annotation.PostMapping; | ||
| 10 | import org.springframework.web.bind.annotation.RequestMapping; | 10 | import org.springframework.web.bind.annotation.RequestMapping; |
| 11 | import org.springframework.web.bind.annotation.RestController; | 11 | import org.springframework.web.bind.annotation.RestController; |
| 12 | 12 | ||
| @@ -37,7 +37,7 @@ public class PtzController { | @@ -37,7 +37,7 @@ public class PtzController { | ||
| 37 | * @param zoomSpeed | 37 | * @param zoomSpeed |
| 38 | * @return | 38 | * @return |
| 39 | */ | 39 | */ |
| 40 | - @GetMapping("/ptz/{deviceId}_{channelId}") | 40 | + @PostMapping("/ptz/{deviceId}/{channelId}") |
| 41 | public ResponseEntity<String> ptz(@PathVariable String deviceId,@PathVariable String channelId,int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed){ | 41 | public ResponseEntity<String> ptz(@PathVariable String deviceId,@PathVariable String channelId,int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed){ |
| 42 | 42 | ||
| 43 | if (logger.isDebugEnabled()) { | 43 | if (logger.isDebugEnabled()) { |
src/main/java/com/genersoft/iot/vmp/vmanager/record/RecordController.java
| @@ -31,17 +31,18 @@ public class RecordController { | @@ -31,17 +31,18 @@ public class RecordController { | ||
| 31 | @Autowired | 31 | @Autowired |
| 32 | private DeferredResultHolder resultHolder; | 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 | if (logger.isDebugEnabled()) { | 37 | if (logger.isDebugEnabled()) { |
| 38 | logger.debug(String.format("录像信息 API调用,deviceId:%s ,startTime:%s, startTime:%s",deviceId, startTime, endTime)); | 38 | logger.debug(String.format("录像信息 API调用,deviceId:%s ,startTime:%s, startTime:%s",deviceId, startTime, endTime)); |
| 39 | } | 39 | } |
| 40 | 40 | ||
| 41 | Device device = storager.queryVideoDevice(deviceId); | 41 | Device device = storager.queryVideoDevice(deviceId); |
| 42 | - cmder.recordInfoQuery(device, startTime, endTime); | 42 | + cmder.recordInfoQuery(device, channelId, startTime, endTime); |
| 43 | DeferredResult<ResponseEntity<RecordInfo>> result = new DeferredResult<ResponseEntity<RecordInfo>>(); | 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 | return result; | 46 | return result; |
| 46 | } | 47 | } |
| 47 | } | 48 | } |
src/main/resources/application.yml
| 1 | spring: | 1 | spring: |
| 2 | application: | 2 | application: |
| 3 | - name: wvp | ||
| 4 | - # 数据存储方式,暂只支持redis,后续支持jdbc | 3 | + name: iot-vmp-vmanager |
| 4 | + # 影子数据存储方式,支持redis、jdbc | ||
| 5 | database: redis | 5 | database: redis |
| 6 | + # 通信方式,支持kafka、http | ||
| 7 | + communicate: http | ||
| 6 | redis: | 8 | redis: |
| 7 | # Redis服务器IP | 9 | # Redis服务器IP |
| 10 | + #host: 10.24.20.63 | ||
| 8 | host: 127.0.0.1 | 11 | host: 127.0.0.1 |
| 9 | #端口号 | 12 | #端口号 |
| 10 | port: 6379 | 13 | port: 6379 |
| @@ -13,24 +16,49 @@ spring: | @@ -13,24 +16,49 @@ spring: | ||
| 13 | password: | 16 | password: |
| 14 | #超时时间 | 17 | #超时时间 |
| 15 | timeout: 10000 | 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 | datasource: | 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 | username: root | 46 | username: root |
| 20 | - password: 123456 | 47 | + password: Ptjsinspur19.? |
| 21 | type: com.alibaba.druid.pool.DruidDataSource | 48 | type: com.alibaba.druid.pool.DruidDataSource |
| 22 | driver-class-name: com.mysql.jdbc.Driver | 49 | driver-class-name: com.mysql.jdbc.Driver |
| 23 | server: | 50 | server: |
| 24 | port: 8080 | 51 | port: 8080 |
| 25 | sip: | 52 | sip: |
| 26 | - # 本地服务地址 | ||
| 27 | - ip: 192.168.0.3 | ||
| 28 | - server_id: 34020000002000000001 | 53 | + ip: 10.200.64.63 |
| 29 | port: 5060 | 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 | password: admin | 61 | password: admin |
| 33 | media: | 62 | media: |
| 34 | - # ZLMediaServer IP | ||
| 35 | - ip: 192.168.0.4 | 63 | + ip: 10.200.64.88 |
| 36 | port: 10000 | 64 | port: 10000 |
| 37 | \ No newline at end of file | 65 | \ No newline at end of file |