| | |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.context.annotation.Configuration; |
| | | |
| | | @Configuration |
| | | @Configuration("sipConfig") |
| | | public class SipConfig { |
| | | |
| | | @Value("${sip.ip}") |
| | |
| | | private final static Logger logger = LoggerFactory.getLogger(SipLayer.class); |
| | | |
| | | @Autowired |
| | | private SipConfig config; |
| | | private SipConfig sipConfig; |
| | | |
| | | private SipProvider tcpSipProvider; |
| | | |
| | |
| | | |
| | | Properties properties = new Properties(); |
| | | properties.setProperty("javax.sip.STACK_NAME", "GB28181_SIP"); |
| | | properties.setProperty("javax.sip.IP_ADDRESS", config.getSipIp()); |
| | | properties.setProperty("javax.sip.IP_ADDRESS", sipConfig.getSipIp()); |
| | | properties.setProperty("gov.nist.javax.sip.LOG_MESSAGE_CONTENT", "false"); |
| | | /** |
| | | * sip_server_log.log 和 sip_debug_log.log public static final int TRACE_NONE = |
| | |
| | | startTcpListener(); |
| | | startUdpListener(); |
| | | } catch (Exception e) { |
| | | logger.error("Sip Server 启动失败! port {" + config.getSipPort() + "}"); |
| | | logger.error("Sip Server 启动失败! port {" + sipConfig.getSipPort() + "}"); |
| | | e.printStackTrace(); |
| | | } |
| | | logger.info("Sip Server 启动成功 port {" + config.getSipPort() + "}"); |
| | | logger.info("Sip Server 启动成功 port {" + sipConfig.getSipPort() + "}"); |
| | | } |
| | | |
| | | private void startTcpListener() throws Exception { |
| | | ListeningPoint tcpListeningPoint = sipStack.createListeningPoint(config.getSipIp(), config.getSipPort(), "TCP"); |
| | | ListeningPoint tcpListeningPoint = sipStack.createListeningPoint(sipConfig.getSipIp(), sipConfig.getSipPort(), "TCP"); |
| | | tcpSipProvider = sipStack.createSipProvider(tcpListeningPoint); |
| | | tcpSipProvider.addSipListener(this); |
| | | } |
| | | |
| | | private void startUdpListener() throws Exception { |
| | | ListeningPoint udpListeningPoint = sipStack.createListeningPoint(config.getSipIp(), config.getSipPort(), "UDP"); |
| | | ListeningPoint udpListeningPoint = sipStack.createListeningPoint(sipConfig.getSipIp(), sipConfig.getSipPort(), "UDP"); |
| | | udpSipProvider = sipStack.createSipProvider(udpListeningPoint); |
| | | udpSipProvider.addSipListener(this); |
| | | } |
| | |
| | | int status = response.getStatusCode(); |
| | | if ((status >= 200) && (status < 300)) { // Success! |
| | | ISIPResponseProcessor processor = processorFactory.createResponseProcessor(evt); |
| | | processor.process(evt, this, config); |
| | | processor.process(evt, this, sipConfig); |
| | | } else { |
| | | logger.warn("接收到失败的response响应!status:" + status + ",message:" + response.getContent().toString()); |
| | | } |
| | |
| | |
|
| | | private String type;
|
| | |
|
| | | private String recordId;
|
| | | private String recorderId;
|
| | |
|
| | | public String getDeviceId() {
|
| | | return deviceId;
|
| | |
| | | this.type = type;
|
| | | }
|
| | |
|
| | | public String getRecordId() {
|
| | | return recordId;
|
| | | public String getRecorderId() {
|
| | | return recorderId;
|
| | | }
|
| | |
|
| | | public void setRecordId(String recordId) {
|
| | | this.recordId = recordId;
|
| | | public void setRecordId(String recorderId) {
|
| | | this.recorderId = recorderId;
|
| | | }
|
| | |
|
| | | public String getEndTime() {
|
| | |
| | | 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);
|
| | | |
| | | /**
|
| | | * 语音广播
|
| | | *
|
| | | * @param device 视频设备
|
| | |
| | | * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
|
| | | * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
|
| | | */
|
| | | public boolean recordInfoQuery(Device device, String startTime, String endTime);
|
| | | public boolean recordInfoQuery(Device device, String channelId, String startTime, String endTime);
|
| | |
|
| | | /**
|
| | | * 查询报警信息
|
| | |
| | | private SipLayer layer;
|
| | |
|
| | | @Autowired
|
| | | private SipConfig config;
|
| | | private SipConfig sipConfig;
|
| | |
|
| | | public Request createMessageRequest(Device device, String content, String viaTag, String fromTag, String toTag) throws ParseException, InvalidArgumentException {
|
| | | Request request = null;
|
| | |
| | | SipURI requestURI = layer.getAddressFactory().createSipURI(device.getDeviceId(), host.getAddress());
|
| | | // via
|
| | | ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
|
| | | ViaHeader viaHeader = layer.getHeaderFactory().createViaHeader(config.getSipIp(), config.getSipPort(),
|
| | | ViaHeader viaHeader = layer.getHeaderFactory().createViaHeader(sipConfig.getSipIp(), sipConfig.getSipPort(),
|
| | | device.getTransport(), viaTag);
|
| | | viaHeaders.add(viaHeader);
|
| | | // from
|
| | | SipURI fromSipURI = layer.getAddressFactory().createSipURI(device.getDeviceId(),
|
| | | config.getSipIp() + ":" + config.getSipPort());
|
| | | sipConfig.getSipIp() + ":" + sipConfig.getSipPort());
|
| | | Address fromAddress = layer.getAddressFactory().createAddress(fromSipURI);
|
| | | FromHeader fromHeader = layer.getHeaderFactory().createFromHeader(fromAddress, fromTag);
|
| | | // to
|
| | |
| | | SipURI requestLine = layer.getAddressFactory().createSipURI(device.getDeviceId(), host.getAddress());
|
| | | //via
|
| | | ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
|
| | | ViaHeader viaHeader = layer.getHeaderFactory().createViaHeader(config.getSipIp(), config.getSipPort(), device.getTransport(), viaTag);
|
| | | ViaHeader viaHeader = layer.getHeaderFactory().createViaHeader(sipConfig.getSipIp(), sipConfig.getSipPort(), device.getTransport(), viaTag);
|
| | | viaHeader.setRPort();
|
| | | viaHeaders.add(viaHeader);
|
| | | //from
|
| | | SipURI fromSipURI = layer.getAddressFactory().createSipURI(device.getDeviceId(),config.getSipIp()+":"+config.getSipPort());
|
| | | SipURI fromSipURI = layer.getAddressFactory().createSipURI(device.getDeviceId(),sipConfig.getSipIp()+":"+sipConfig.getSipPort());
|
| | | Address fromAddress = layer.getAddressFactory().createAddress(fromSipURI);
|
| | | FromHeader fromHeader = layer.getHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack
|
| | | //to
|
| | |
| | | import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
|
| | | import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider;
|
| | | import com.genersoft.iot.vmp.gb28181.utils.DateUtil;
|
| | | import com.genersoft.iot.vmp.gb28181.utils.SsrcUtil;
|
| | |
|
| | | import tk.mybatis.mapper.util.StringUtil;
|
| | |
|
| | | /**
|
| | | * @Description:设备能力接口,用于定义设备的控制、查询能力
|
| | |
| | | public class SIPCommander implements ISIPCommander {
|
| | |
|
| | | @Autowired
|
| | | private SipConfig config;
|
| | | private SipConfig sipConfig;
|
| | |
|
| | | @Autowired
|
| | | private SIPRequestHeaderProvider headerProvider;
|
| | |
| | | */
|
| | | @Override
|
| | | public boolean ptzdirectCmd(Device device, String channelId, int leftRight, int upDown) {
|
| | | return ptzCmd(device, channelId, leftRight, upDown, 0, config.getSpeed(), 0);
|
| | | return ptzCmd(device, channelId, leftRight, upDown, 0, sipConfig.getSpeed(), 0);
|
| | | }
|
| | |
|
| | | /**
|
| | |
| | | */
|
| | | @Override
|
| | | public boolean ptzZoomCmd(Device device, String channelId, int inOut) {
|
| | | return ptzCmd(device, channelId, 0, 0, inOut, 0, config.getSpeed());
|
| | | return ptzCmd(device, channelId, 0, 0, inOut, 0, sipConfig.getSpeed());
|
| | | }
|
| | |
|
| | | /**
|
| | |
| | | public String playStreamCmd(Device device, String channelId) {
|
| | | try {
|
| | |
|
| | | //生成ssrc标识数据流 10位数字
|
| | | String ssrc = "";
|
| | | Random random = new Random();
|
| | | // ZLMediaServer最大识别7FFFFFFF即2147483647,所以随机数不能超过这个数
|
| | | ssrc = String.valueOf(random.nextInt(2147483647));
|
| | | String ssrc = SsrcUtil.getPlaySsrc();
|
| | | //
|
| | | StringBuffer content = new StringBuffer(200);
|
| | | content.append("v=0\r\n");
|
| | | content.append("o="+channelId+" 0 0 IN IP4 "+config.getSipIp()+"\r\n");
|
| | | content.append("o="+channelId+" 0 0 IN IP4 "+sipConfig.getSipIp()+"\r\n");
|
| | | content.append("s=Play\r\n");
|
| | | content.append("c=IN IP4 "+config.getMediaIp()+"\r\n");
|
| | | content.append("c=IN IP4 "+sipConfig.getMediaIp()+"\r\n");
|
| | | content.append("t=0 0\r\n");
|
| | | if(device.getTransport().equals("TCP")) {
|
| | | content.append("m=video "+config.getMediaPort()+" TCP/RTP/AVP 96 98 97\r\n");
|
| | | content.append("m=video "+sipConfig.getMediaPort()+" TCP/RTP/AVP 96 98 97\r\n");
|
| | | }
|
| | | if(device.getTransport().equals("UDP")) {
|
| | | content.append("m=video "+config.getMediaPort()+" RTP/AVP 96 98 97\r\n");
|
| | | content.append("m=video "+sipConfig.getMediaPort()+" RTP/AVP 96 98 97\r\n");
|
| | | }
|
| | | content.append("a=sendrecv\r\n");
|
| | | content.append("a=rtpmap:96 PS/90000\r\n");
|
| | |
| | | e.printStackTrace();
|
| | | return null;
|
| | | }
|
| | | }
|
| | | |
| | | /**
|
| | | * 请求回放视频流
|
| | | * |
| | | * @param device 视频设备
|
| | | * @param channelId 预览通道
|
| | | * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
|
| | | * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
|
| | | */ |
| | | @Override
|
| | | public String playbackStreamCmd(Device device, String channelId, String recordId, String startTime, String endTime) {
|
| | | try {
|
| | | |
| | | String ssrc = SsrcUtil.getPlayBackSsrc();
|
| | | //
|
| | | StringBuffer content = new StringBuffer(200);
|
| | | content.append("v=0\r\n");
|
| | | content.append("o="+channelId+" 0 0 IN IP4 "+sipConfig.getSipIp()+"\r\n");
|
| | | content.append("s=Playback\r\n");
|
| | | content.append("u="+recordId+":3\r\n");
|
| | | content.append("c=IN IP4 "+sipConfig.getMediaIp()+"\r\n");
|
| | | content.append("t="+DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime)+" "+DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime) +"\r\n");
|
| | | if(device.getTransport().equals("TCP")) {
|
| | | content.append("m=video "+sipConfig.getMediaPort()+" TCP/RTP/AVP 96 98 97\r\n");
|
| | | }
|
| | | if(device.getTransport().equals("UDP")) {
|
| | | content.append("m=video "+sipConfig.getMediaPort()+" RTP/AVP 96 98 97\r\n");
|
| | | }
|
| | | content.append("a=recvonly\r\n");
|
| | | content.append("a=rtpmap:96 PS/90000\r\n");
|
| | | content.append("a=rtpmap:98 H264/90000\r\n");
|
| | | content.append("a=rtpmap:97 MPEG4/90000\r\n");
|
| | | if(device.getTransport().equals("TCP")){
|
| | | content.append("a=setup:passive\r\n");
|
| | | content.append("a=connection:new\r\n");
|
| | | }
|
| | | content.append("y="+ssrc+"\r\n");//ssrc
|
| | | |
| | | Request request = headerProvider.createInviteRequest(device, content.toString(), null, "live", null);
|
| | | |
| | | transmitRequest(device, request);
|
| | | return ssrc;
|
| | | } catch ( SipException | ParseException | InvalidArgumentException e) {
|
| | | e.printStackTrace();
|
| | | return null;
|
| | | }
|
| | | }
|
| | |
|
| | | /**
|
| | |
| | | * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
|
| | | */
|
| | | @Override
|
| | | public boolean recordInfoQuery(Device device, String startTime, String endTime) {
|
| | | public boolean recordInfoQuery(Device device, String channelId, String startTime, String endTime) {
|
| | |
|
| | | try {
|
| | | StringBuffer catalogXml = new StringBuffer(200);
|
| | | catalogXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>");
|
| | | catalogXml.append("<Query>");
|
| | | catalogXml.append("<CmdType>RecordInfo</CmdType>");
|
| | | catalogXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>");
|
| | | catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>");
|
| | | catalogXml.append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(startTime) + "</StartTime>");
|
| | | catalogXml.append("<EndTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(endTime) + "</EndTime>");
|
| | | StringBuffer recordInfoXml = new StringBuffer(200);
|
| | | recordInfoXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>");
|
| | | recordInfoXml.append("<Query>");
|
| | | recordInfoXml.append("<CmdType>RecordInfo</CmdType>");
|
| | | recordInfoXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>");
|
| | | recordInfoXml.append("<DeviceID>" + channelId + "</DeviceID>");
|
| | | recordInfoXml.append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(startTime) + "</StartTime>");
|
| | | recordInfoXml.append("<EndTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(endTime) + "</EndTime>");
|
| | | recordInfoXml.append("<Secrecy>0</Secrecy>");
|
| | | // 大华NVR要求必须增加一个值为all的文本元素节点Type
|
| | | catalogXml.append("<Type>all</Type>");
|
| | | catalogXml.append("</Query>");
|
| | | recordInfoXml.append("<Type>all</Type>");
|
| | | recordInfoXml.append("</Query>");
|
| | |
|
| | | Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "ViaRecordInfoBranch", "FromRecordInfoTag", "ToRecordInfoTag");
|
| | | Request request = headerProvider.createMessageRequest(device, recordInfoXml.toString(), "ViaRecordInfoBranch", "FromRecordInfoTag", "ToRecordInfoTag");
|
| | | transmitRequest(device, request);
|
| | | } catch (SipException | ParseException | InvalidArgumentException e) {
|
| | | e.printStackTrace();
|
| | |
| | | sipLayer.getUdpSipProvider().sendRequest(request);
|
| | | }
|
| | | }
|
| | |
|
| | | }
|
| | |
| | | import org.dom4j.DocumentException;
|
| | | import org.dom4j.Element;
|
| | | import org.dom4j.io.SAXReader;
|
| | | import org.slf4j.Logger;
|
| | | import org.slf4j.LoggerFactory;
|
| | | import org.springframework.beans.factory.annotation.Autowired;
|
| | | import org.springframework.stereotype.Component;
|
| | |
|
| | |
| | | import com.genersoft.iot.vmp.gb28181.utils.DateUtil;
|
| | | import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
|
| | | import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
|
| | | import com.genersoft.iot.vmp.utils.redis.RedisUtil;
|
| | |
|
| | | /**
|
| | | * @Description:MESSAGE请求处理器
|
| | |
| | | */
|
| | | @Component
|
| | | public class MessageRequestProcessor implements ISIPRequestProcessor {
|
| | |
|
| | | |
| | | private final static Logger logger = LoggerFactory.getLogger(MessageRequestProcessor.class);
|
| | | |
| | | private ServerTransaction transaction;
|
| | |
|
| | | private SipLayer layer;
|
| | |
| | | private EventPublisher publisher;
|
| | |
|
| | | @Autowired
|
| | | private RedisUtil redis;
|
| | | |
| | | @Autowired
|
| | | private DeferredResultHolder deferredResultHolder;
|
| | | |
| | | private final static String CACHE_RECORDINFO_KEY = "CACHE_RECORDINFO_";
|
| | |
|
| | | /**
|
| | | * 处理MESSAGE请求
|
| | |
| | | Request request = evt.getRequest();
|
| | |
|
| | | if (new String(request.getRawContent()).contains("<CmdType>Keepalive</CmdType>")) {
|
| | | logger.info("接收到KeepAlive消息");
|
| | | processMessageKeepAlive(evt);
|
| | | } else if (new String(request.getRawContent()).contains("<CmdType>Catalog</CmdType>")) {
|
| | | logger.info("接收到Catalog消息");
|
| | | processMessageCatalogList(evt);
|
| | | } else if (new String(request.getRawContent()).contains("<CmdType>DeviceInfo</CmdType>")) {
|
| | | logger.info("接收到DeviceInfo消息");
|
| | | processMessageDeviceInfo(evt);
|
| | | } else if (new String(request.getRawContent()).contains("<CmdType>Alarm</CmdType>")) {
|
| | | logger.info("接收到Alarm消息");
|
| | | processMessageAlarm(evt);
|
| | | } else if (new String(request.getRawContent()).contains("<CmdType>recordInfo</CmdType>")) {
|
| | | } else if (new String(request.getRawContent()).contains("<CmdType>RecordInfo</CmdType>")) {
|
| | | logger.info("接收到RecordInfo消息");
|
| | | processMessageRecordInfo(evt);
|
| | | }
|
| | |
|
| | |
| | |
|
| | | /***
|
| | | * 收到catalog设备目录列表请求 处理
|
| | | * TODO 过期时间暂时写死180秒,后续与DeferredResult超时时间保持一致
|
| | | * @param evt
|
| | | */
|
| | | private void processMessageRecordInfo(RequestEvent evt) {
|
| | |
| | | recordInfo.setDeviceId(deviceId);
|
| | | recordInfo.setName(XmlUtil.getText(rootElement,"Name"));
|
| | | recordInfo.setSumNum(Integer.parseInt(XmlUtil.getText(rootElement,"SumNum")));
|
| | | String sn = XmlUtil.getText(rootElement,"SN");
|
| | | Element recordListElement = rootElement.element("RecordList");
|
| | | if (recordListElement == null) {
|
| | | return;
|
| | | }
|
| | |
|
| | | Iterator<Element> recordListIterator = recordListElement.elementIterator();
|
| | | List<RecordItem> recordList = new ArrayList<RecordItem>();
|
| | | if (recordListIterator != null) {
|
| | | |
| | | List<RecordItem> recordList = new ArrayList<RecordItem>();
|
| | | RecordItem record = new RecordItem();
|
| | | // 遍历DeviceList
|
| | | while (recordListIterator.hasNext()) {
|
| | |
| | | if (recordElement == null) {
|
| | | continue;
|
| | | }
|
| | | record = new RecordItem();
|
| | | record.setDeviceId(XmlUtil.getText(itemRecord,"DeviceID"));
|
| | | record.setName(XmlUtil.getText(itemRecord,"Name"));
|
| | | record.setFilePath(XmlUtil.getText(itemRecord,"FilePath"));
|
| | |
| | | record.setEndTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(XmlUtil.getText(itemRecord,"EndTime")));
|
| | | record.setSecrecy(itemRecord.element("Secrecy") == null? 0:Integer.parseInt(XmlUtil.getText(itemRecord,"Secrecy")));
|
| | | record.setType(XmlUtil.getText(itemRecord,"Type"));
|
| | | record.setRecordId(XmlUtil.getText(itemRecord,"RecordID"));
|
| | | record.setRecordId(XmlUtil.getText(itemRecord,"RecorderID"));
|
| | | recordList.add(record);
|
| | | }
|
| | | recordInfo.setRecordList(recordList);
|
| | | }
|
| | |
|
| | | |
| | | // 存在录像且如果当前录像明细个数小于总条数,说明拆包返回,需要组装,暂不返回
|
| | | if (recordInfo.getSumNum() > 0 && recordList.size() > 0 && recordList.size() < recordInfo.getSumNum()) {
|
| | | // 为防止连续请求该设备的录像数据,返回数据错乱,特增加sn进行区分
|
| | | String cacheKey = CACHE_RECORDINFO_KEY+deviceId+sn;
|
| | | // TODO 暂时直接操作redis存储,后续封装专用缓存接口,改为本地内存缓存
|
| | | if (redis.hasKey(cacheKey)) {
|
| | | List<RecordItem> previousList = (List<RecordItem>) redis.get(cacheKey);
|
| | | if (previousList != null && previousList.size() > 0) {
|
| | | recordList.addAll(previousList);
|
| | | }
|
| | | // 本分支表示录像列表被拆包,且加上之前的数据还是不够,保存缓存返回,等待下个包再处理
|
| | | if (recordList.size() < recordInfo.getSumNum()) {
|
| | | redis.set(cacheKey, recordList, 180);
|
| | | return;
|
| | | } else {
|
| | | // 本分支表示录像被拆包,但加上之前的数据够足够,返回响应
|
| | | // 因设备心跳有监听redis过期机制,为提高性能,此处手动删除
|
| | | redis.del(cacheKey);
|
| | | }
|
| | | } else {
|
| | | // 本分支有两种可能:1、录像列表被拆包,且是第一个包,直接保存缓存返回,等待下个包再处理
|
| | | // 2、之前有包,但超时清空了,那么这次sn批次的响应数据已经不完整,等待过期时间后redis自动清空数据
|
| | | redis.set(cacheKey, recordList, 180);
|
| | | return;
|
| | | }
|
| | | |
| | | }
|
| | | // 走到这里,有以下可能:1、没有录像信息,第一次收到recordinfo的消息即返回响应数据,无redis操作
|
| | | // 2、有录像数据,且第一次即收到完整数据,返回响应数据,无redis操作
|
| | | // 3、有录像数据,在超时时间内收到多次包组装后数量足够,返回数据
|
| | | RequestMessage msg = new RequestMessage();
|
| | | msg.setDeviceId(deviceId);
|
| | | msg.setType(DeferredResultHolder.CALLBACK_CMD_RECORDINFO);
|
| | |
| | | public class RegisterRequestProcessor implements ISIPRequestProcessor {
|
| | |
|
| | | @Autowired
|
| | | private SipConfig config;
|
| | | private SipConfig sipConfig;
|
| | |
|
| | | @Autowired
|
| | | private RegisterLogicHandler handler;
|
| | |
| | | // 校验密码是否正确
|
| | | if (authorhead != null) {
|
| | | passwordCorrect = new DigestServerAuthenticationHelper().doAuthenticatePlainTextPassword(request,
|
| | | config.getSipPassword());
|
| | | sipConfig.getSipPassword());
|
| | | }
|
| | |
|
| | | // 未携带授权头或者密码错误 回复401
|
| | |
| | | System.out.println("密码错误 回复401");
|
| | | }
|
| | | response = layer.getMessageFactory().createResponse(Response.UNAUTHORIZED, request);
|
| | | new DigestServerAuthenticationHelper().generateChallenge(layer.getHeaderFactory(), response, config.getSipDomain());
|
| | | new DigestServerAuthenticationHelper().generateChallenge(layer.getHeaderFactory(), response, sipConfig.getSipDomain());
|
| | | }
|
| | | // 携带授权头并且密码正确
|
| | | else if (passwordCorrect) {
|
| | |
| | |
|
| | | import java.text.ParseException;
|
| | | import java.text.SimpleDateFormat;
|
| | | import java.util.Date;
|
| | | import java.util.Locale;
|
| | |
|
| | | /**
|
| | |
| | | */
|
| | | public class DateUtil {
|
| | |
|
| | | private static final String yyyy_MM_dd_T_HH_mm_ss_SSSXXX = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
|
| | | //private static final String yyyy_MM_dd_T_HH_mm_ss_SSSXXX = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
|
| | | private static final String yyyy_MM_dd_T_HH_mm_ss_SSSXXX = "yyyy-MM-dd'T'HH:mm:ss";
|
| | | private static final String yyyy_MM_dd_HH_mm_ss = "yyyy-MM-dd HH:mm:ss";
|
| | |
|
| | | public static String yyyy_MM_dd_HH_mm_ssToISO8601(String formatTime) {
|
| | |
| | | }
|
| | | 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;
|
| | | }
|
| | | }
|
New file |
| | |
| | | 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;
|
| | | }
|
| | | }
|
New file |
| | |
| | | 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];
|
| | | }
|
| | |
|
| | | }
|
| | |
| | | package com.genersoft.iot.vmp.vmanager.device; |
| | | |
| | | import java.util.List; |
| | | import java.util.concurrent.ExecutorService; |
| | | import java.util.concurrent.Executors; |
| | | |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
New file |
| | |
| | | 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); |
| | | } |
| | | } |
| | | } |
| | |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.http.HttpStatus; |
| | | import org.springframework.http.ResponseEntity; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.PathVariable; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | |
| | | * @param zoomSpeed |
| | | * @return |
| | | */ |
| | | @GetMapping("/ptz/{deviceId}_{channelId}") |
| | | @PostMapping("/ptz/{deviceId}/{channelId}") |
| | | public ResponseEntity<String> ptz(@PathVariable String deviceId,@PathVariable String channelId,int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed){ |
| | | |
| | | if (logger.isDebugEnabled()) { |
| | |
| | | @Autowired |
| | | private DeferredResultHolder resultHolder; |
| | | |
| | | @GetMapping("/recordinfo/{deviceId}") |
| | | public DeferredResult<ResponseEntity<RecordInfo>> recordinfo(@PathVariable String deviceId, String startTime, String endTime){ |
| | | @GetMapping("/record/{deviceId}") |
| | | public DeferredResult<ResponseEntity<RecordInfo>> recordinfo(@PathVariable String deviceId, String channelId, String startTime, String endTime){ |
| | | |
| | | if (logger.isDebugEnabled()) { |
| | | logger.debug(String.format("录像信息 API调用,deviceId:%s ,startTime:%s, startTime:%s",deviceId, startTime, endTime)); |
| | | } |
| | | |
| | | Device device = storager.queryVideoDevice(deviceId); |
| | | cmder.recordInfoQuery(device, startTime, endTime); |
| | | cmder.recordInfoQuery(device, channelId, startTime, endTime); |
| | | DeferredResult<ResponseEntity<RecordInfo>> result = new DeferredResult<ResponseEntity<RecordInfo>>(); |
| | | resultHolder.put(DeferredResultHolder.CALLBACK_CMD_CATALOG+deviceId, result); |
| | | // 录像查询以channelId作为deviceId查询 |
| | | resultHolder.put(DeferredResultHolder.CALLBACK_CMD_RECORDINFO+channelId, result); |
| | | return result; |
| | | } |
| | | } |
| | |
| | | spring: |
| | | application: |
| | | name: wvp |
| | | # 数据存储方式,暂只支持redis,后续支持jdbc |
| | | name: iot-vmp-vmanager |
| | | # 影子数据存储方式,支持redis、jdbc |
| | | database: redis |
| | | # 通信方式,支持kafka、http |
| | | communicate: http |
| | | redis: |
| | | # Redis服务器IP |
| | | #host: 10.24.20.63 |
| | | host: 127.0.0.1 |
| | | #端口号 |
| | | port: 6379 |
| | |
| | | password: |
| | | #超时时间 |
| | | timeout: 10000 |
| | | # 可用连接实例的最大数目,默认值为8 |
| | | maxTotal: 512 |
| | | #控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8 |
| | | maxIdle: 100 |
| | | #最小空闲连接数 |
| | | minIdle: 50 |
| | | #获取连接时的最大等待毫秒数,小于零:阻塞不确定的时间,默认-1 |
| | | maxWaitMillis: 10000 |
| | | #每次释放连接的最大数目 |
| | | numTestsPerEvictionRun: 100 |
| | | #释放连接的扫描间隔(毫秒) |
| | | timeBetweenEvictionRunsMillis: 3000 |
| | | #连接最小空闲时间 |
| | | minEvictableIdleTimeMillis: 1800000 |
| | | #连接空闲多久后释放,当空闲时间>该值且空闲连接>最大空闲连接数时直接释放 |
| | | softMinEvictableIdleTimeMillis: 10000 |
| | | #在获取连接的时候检查有效性,默认false |
| | | testOnBorrow: true |
| | | #在空闲时检查有效性,默认false |
| | | testWhileIdle: true |
| | | #在归还给pool时,是否提前进行validate操作 |
| | | testOnReturn: true |
| | | #连接耗尽时是否阻塞,false报异常,ture阻塞直到超时,默认true |
| | | blockWhenExhausted: false |
| | | datasource: |
| | | name: wcp |
| | | url: jdbc:mysql://127.0.0.1:3306/wcp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true |
| | | name: eiot |
| | | url: jdbc:mysql://10.24.20.63:3306/eiot?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true |
| | | username: root |
| | | password: 123456 |
| | | password: Ptjsinspur19.? |
| | | type: com.alibaba.druid.pool.DruidDataSource |
| | | driver-class-name: com.mysql.jdbc.Driver |
| | | server: |
| | | port: 8080 |
| | | sip: |
| | | # 本地服务地址 |
| | | ip: 192.168.0.3 |
| | | server_id: 34020000002000000001 |
| | | ip: 10.200.64.63 |
| | | port: 5060 |
| | | domain: 34020000 |
| | | # 暂时使用统一密码,后续改为一机一密 |
| | | # 根据国标6.1.2中规定,domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码(由省级、市级、区级、基层编号组成,参照GB/T 2260-2007) |
| | | # 后两位为行业编码,定义参照附录D.3 |
| | | # 3701020049标识山东济南历下区 信息行业接入 |
| | | domain: 3701020049 |
| | | server_id: 37010200492000000001 |
| | | # 默认设备认证密码,后续扩展使用设备单独密码 |
| | | password: admin |
| | | media: |
| | | # ZLMediaServer IP |
| | | ip: 192.168.0.4 |
| | | ip: 10.200.64.88 |
| | | port: 10000 |