songww
2020-05-10 3a502b36a8cfcdd455edb22e5771115559873731
完善ssrc符合国标,并完善很多小问题
13个文件已修改
3个文件已添加
479 ■■■■ 已修改文件
src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordItem.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java 96 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java 58 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/RegisterRequestProcessor.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/utils/DateUtil.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/utils/SsrcUtil.java 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/utils/SpringBeanFactory.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/playback/PlaybackController.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/ptz/PtzController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/record/RecordController.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application.yml 52 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java
@@ -3,7 +3,7 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Configuration
@Configuration("sipConfig")
public class SipConfig {
    @Value("${sip.ip}")
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java
@@ -42,7 +42,7 @@
    private final static Logger logger = LoggerFactory.getLogger(SipLayer.class);
    @Autowired
    private SipConfig config;
    private SipConfig sipConfig;
    private SipProvider tcpSipProvider;
@@ -77,7 +77,7 @@
            Properties properties = new Properties();
            properties.setProperty("javax.sip.STACK_NAME", "GB28181_SIP");
            properties.setProperty("javax.sip.IP_ADDRESS", config.getSipIp());
            properties.setProperty("javax.sip.IP_ADDRESS", sipConfig.getSipIp());
            properties.setProperty("gov.nist.javax.sip.LOG_MESSAGE_CONTENT", "false");
            /**
             * sip_server_log.log 和 sip_debug_log.log public static final int TRACE_NONE =
@@ -92,20 +92,20 @@
            startTcpListener();
            startUdpListener();
        } catch (Exception e) {
            logger.error("Sip Server 启动失败! port {" + config.getSipPort() + "}");
            logger.error("Sip Server 启动失败! port {" + sipConfig.getSipPort() + "}");
            e.printStackTrace();
        }
        logger.info("Sip Server 启动成功 port {" + config.getSipPort() + "}");
        logger.info("Sip Server 启动成功 port {" + sipConfig.getSipPort() + "}");
    }
    private void startTcpListener() throws Exception {
        ListeningPoint tcpListeningPoint = sipStack.createListeningPoint(config.getSipIp(), config.getSipPort(), "TCP");
        ListeningPoint tcpListeningPoint = sipStack.createListeningPoint(sipConfig.getSipIp(), sipConfig.getSipPort(), "TCP");
        tcpSipProvider = sipStack.createSipProvider(tcpListeningPoint);
        tcpSipProvider.addSipListener(this);
    }
    private void startUdpListener() throws Exception {
        ListeningPoint udpListeningPoint = sipStack.createListeningPoint(config.getSipIp(), config.getSipPort(), "UDP");
        ListeningPoint udpListeningPoint = sipStack.createListeningPoint(sipConfig.getSipIp(), sipConfig.getSipPort(), "UDP");
        udpSipProvider = sipStack.createSipProvider(udpListeningPoint);
        udpSipProvider.addSipListener(this);
    }
@@ -126,7 +126,7 @@
        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());
        }
src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordItem.java
@@ -23,7 +23,7 @@
    
    private String type;
    
    private String recordId;
    private String recorderId;
    public String getDeviceId() {
        return deviceId;
@@ -81,12 +81,12 @@
        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() {
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
@@ -72,6 +72,16 @@
    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  视频设备
@@ -153,7 +163,7 @@
     * @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);
    
    /**
     * 查询报警信息
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java
@@ -35,7 +35,7 @@
    private SipLayer layer;
    @Autowired
    private SipConfig config;
    private SipConfig sipConfig;
    
    public Request createMessageRequest(Device device, String content, String viaTag, String fromTag, String toTag) throws ParseException, InvalidArgumentException {
        Request request = null;
@@ -44,12 +44,12 @@
        SipURI requestURI = layer.getAddressFactory().createSipURI(device.getDeviceId(), host.getAddress());
        // via
        ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
        ViaHeader viaHeader = layer.getHeaderFactory().createViaHeader(config.getSipIp(), config.getSipPort(),
        ViaHeader viaHeader = layer.getHeaderFactory().createViaHeader(sipConfig.getSipIp(), sipConfig.getSipPort(),
                device.getTransport(), viaTag);
        viaHeaders.add(viaHeader);
        // from
        SipURI fromSipURI = layer.getAddressFactory().createSipURI(device.getDeviceId(),
                config.getSipIp() + ":" + config.getSipPort());
                sipConfig.getSipIp() + ":" + sipConfig.getSipPort());
        Address fromAddress = layer.getAddressFactory().createAddress(fromSipURI);
        FromHeader fromHeader = layer.getHeaderFactory().createFromHeader(fromAddress, fromTag);
        // to
@@ -78,11 +78,11 @@
        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
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
@@ -17,6 +17,9 @@
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider;
import com.genersoft.iot.vmp.gb28181.utils.DateUtil;
import com.genersoft.iot.vmp.gb28181.utils.SsrcUtil;
import tk.mybatis.mapper.util.StringUtil;
/**    
 * @Description:设备能力接口,用于定义设备的控制、查询能力   
@@ -27,7 +30,7 @@
public class SIPCommander implements ISIPCommander {
    
    @Autowired
    private SipConfig config;
    private SipConfig sipConfig;
    
    @Autowired
    private SIPRequestHeaderProvider headerProvider;
@@ -46,7 +49,7 @@
     */
    @Override
    public boolean ptzdirectCmd(Device device, String channelId, int leftRight, int upDown) {
        return ptzCmd(device, channelId, leftRight, upDown, 0, config.getSpeed(), 0);
        return ptzCmd(device, channelId, leftRight, upDown, 0, sipConfig.getSpeed(), 0);
    }
    /**
@@ -72,7 +75,7 @@
     */  
    @Override
    public boolean ptzZoomCmd(Device device, String channelId, int inOut) {
        return ptzCmd(device, channelId, 0, 0, inOut, 0, config.getSpeed());
        return ptzCmd(device, channelId, 0, 0, inOut, 0, sipConfig.getSpeed());
    }
    /**
@@ -135,23 +138,19 @@
    public 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");
@@ -171,6 +170,53 @@
            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;
        }
    }
    /**
@@ -323,22 +369,23 @@
     * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
     */  
    @Override
    public boolean recordInfoQuery(Device device, String startTime, String endTime) {
    public boolean recordInfoQuery(Device device, String channelId, String startTime, String endTime) {
        
        try {
            StringBuffer catalogXml = new StringBuffer(200);
            catalogXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>");
            catalogXml.append("<Query>");
            catalogXml.append("<CmdType>RecordInfo</CmdType>");
            catalogXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>");
            catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>");
            catalogXml.append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(startTime) + "</StartTime>");
            catalogXml.append("<EndTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(endTime) + "</EndTime>");
            StringBuffer recordInfoXml = new StringBuffer(200);
            recordInfoXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>");
            recordInfoXml.append("<Query>");
            recordInfoXml.append("<CmdType>RecordInfo</CmdType>");
            recordInfoXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>");
            recordInfoXml.append("<DeviceID>" + channelId + "</DeviceID>");
            recordInfoXml.append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(startTime) + "</StartTime>");
            recordInfoXml.append("<EndTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(endTime) + "</EndTime>");
            recordInfoXml.append("<Secrecy>0</Secrecy>");
            // 大华NVR要求必须增加一个值为all的文本元素节点Type
            catalogXml.append("<Type>all</Type>");
            catalogXml.append("</Query>");
            recordInfoXml.append("<Type>all</Type>");
            recordInfoXml.append("</Query>");
            
            Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "ViaRecordInfoBranch", "FromRecordInfoTag", "ToRecordInfoTag");
            Request request = headerProvider.createMessageRequest(device, recordInfoXml.toString(), "ViaRecordInfoBranch", "FromRecordInfoTag", "ToRecordInfoTag");
            transmitRequest(device, request);
        } catch (SipException | ParseException | InvalidArgumentException e) {
            e.printStackTrace();
@@ -398,4 +445,5 @@
            sipLayer.getUdpSipProvider().sendRequest(request);
        }
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java
@@ -19,6 +19,8 @@
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -36,6 +38,7 @@
import com.genersoft.iot.vmp.gb28181.utils.DateUtil;
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
/**    
 * @Description:MESSAGE请求处理器
@@ -44,7 +47,9 @@
 */
@Component
public class MessageRequestProcessor implements ISIPRequestProcessor {
    private final static Logger logger = LoggerFactory.getLogger(MessageRequestProcessor.class);
    private ServerTransaction transaction;
    
    private SipLayer layer;
@@ -59,7 +64,12 @@
    private EventPublisher publisher;
    
    @Autowired
    private RedisUtil redis;
    @Autowired
    private DeferredResultHolder deferredResultHolder;
    private final static String CACHE_RECORDINFO_KEY = "CACHE_RECORDINFO_";
    
    /**   
     * 处理MESSAGE请求
@@ -77,14 +87,19 @@
        Request request = evt.getRequest();
        
        if (new String(request.getRawContent()).contains("<CmdType>Keepalive</CmdType>")) {
            logger.info("接收到KeepAlive消息");
            processMessageKeepAlive(evt);
        } else if (new String(request.getRawContent()).contains("<CmdType>Catalog</CmdType>")) {
            logger.info("接收到Catalog消息");
            processMessageCatalogList(evt);
        } else if (new String(request.getRawContent()).contains("<CmdType>DeviceInfo</CmdType>")) {
            logger.info("接收到DeviceInfo消息");
            processMessageDeviceInfo(evt);
        } else if (new String(request.getRawContent()).contains("<CmdType>Alarm</CmdType>")) {
            logger.info("接收到Alarm消息");
            processMessageAlarm(evt);
        } else if (new String(request.getRawContent()).contains("<CmdType>recordInfo</CmdType>")) {
        } else if (new String(request.getRawContent()).contains("<CmdType>RecordInfo</CmdType>")) {
            logger.info("接收到RecordInfo消息");
            processMessageRecordInfo(evt);
        }
        
@@ -245,6 +260,7 @@
    
    /***
     * 收到catalog设备目录列表请求 处理
     * TODO 过期时间暂时写死180秒,后续与DeferredResult超时时间保持一致
     * @param evt
     */
    private void processMessageRecordInfo(RequestEvent evt) {
@@ -256,15 +272,15 @@
            recordInfo.setDeviceId(deviceId);
            recordInfo.setName(XmlUtil.getText(rootElement,"Name"));
            recordInfo.setSumNum(Integer.parseInt(XmlUtil.getText(rootElement,"SumNum")));
            String sn = XmlUtil.getText(rootElement,"SN");
            Element recordListElement = rootElement.element("RecordList");
            if (recordListElement == null) {
                return;
            }
            
            Iterator<Element> recordListIterator = recordListElement.elementIterator();
            List<RecordItem> recordList = new ArrayList<RecordItem>();
            if (recordListIterator != null) {
                List<RecordItem> recordList = new ArrayList<RecordItem>();
                RecordItem record = new RecordItem();
                // 遍历DeviceList
                while (recordListIterator.hasNext()) {
@@ -273,6 +289,7 @@
                    if (recordElement == null) {
                        continue;
                    }
                    record = new RecordItem();
                    record.setDeviceId(XmlUtil.getText(itemRecord,"DeviceID"));
                    record.setName(XmlUtil.getText(itemRecord,"Name"));
                    record.setFilePath(XmlUtil.getText(itemRecord,"FilePath"));
@@ -281,13 +298,42 @@
                    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);
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/RegisterRequestProcessor.java
@@ -45,7 +45,7 @@
public class RegisterRequestProcessor implements ISIPRequestProcessor {
    @Autowired
    private SipConfig config;
    private SipConfig sipConfig;
    
    @Autowired
    private RegisterLogicHandler handler;
@@ -77,7 +77,7 @@
            // 校验密码是否正确
            if (authorhead != null) {
                passwordCorrect = new DigestServerAuthenticationHelper().doAuthenticatePlainTextPassword(request,
                        config.getSipPassword());
                        sipConfig.getSipPassword());
            }
            // 未携带授权头或者密码错误 回复401
@@ -89,7 +89,7 @@
                    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) {
src/main/java/com/genersoft/iot/vmp/gb28181/utils/DateUtil.java
@@ -2,6 +2,7 @@
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**    
@@ -11,7 +12,8 @@
 */
public class DateUtil {
    private static final String yyyy_MM_dd_T_HH_mm_ss_SSSXXX = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
    //private static final String yyyy_MM_dd_T_HH_mm_ss_SSSXXX = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
    private static final String yyyy_MM_dd_T_HH_mm_ss_SSSXXX = "yyyy-MM-dd'T'HH:mm:ss";
    private static final String yyyy_MM_dd_HH_mm_ss = "yyyy-MM-dd HH:mm:ss";
    
    public static String yyyy_MM_dd_HH_mm_ssToISO8601(String formatTime) {
@@ -37,4 +39,19 @@
        }
        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;
    }
}
src/main/java/com/genersoft/iot/vmp/gb28181/utils/SsrcUtil.java
New file
@@ -0,0 +1,91 @@
package com.genersoft.iot.vmp.gb28181.utils;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.utils.SpringBeanFactory;
/**
 * @Description:SIP信令中的SSRC工具类。SSRC值由10位十进制整数组成的字符串,第一位为0代表实况,为1则代表回放;第二位至第六位由监控域ID的第4位到第8位组成;最后4位为不重复的4个整数
 * @author: songww
 * @date: 2020年5月10日 上午11:57:57
 */
public class SsrcUtil {
    private static String ssrcPrefix;
    private static List<String> isUsed;
    private static List<String> notUsed;
    private static void init() {
        SipConfig sipConfig = (SipConfig) SpringBeanFactory.getBean("sipConfig");
        ssrcPrefix = sipConfig.getSipDomain().substring(4, 9);
        isUsed = new ArrayList<String>();
        notUsed = new ArrayList<String>();
        for (int i = 1; i < 10000; i++) {
            if (i < 10) {
                notUsed.add("000" + i);
            } else if (i < 100) {
                notUsed.add("00" + i);
            } else if (i < 1000) {
                notUsed.add("0" + i);
            } else {
                notUsed.add(String.valueOf(i));
            }
        }
    }
    /**
     * 获取视频预览的SSRC值,第一位固定为0
     *
     */
    public static String getPlaySsrc() {
        return "0" + getSsrcPrefix() + getSN();
    }
    /**
     * 获取录像回放的SSRC值,第一位固定为1
     *
     */
    public static String getPlayBackSsrc() {
        return "1" + getSsrcPrefix() + getSN();
    }
    /**
     * 释放ssrc,主要用完的ssrc一定要释放,否则会耗尽
     *
     */
    public static void releaseSsrc(String ssrc) {
        String sn = ssrc.substring(6);
        isUsed.remove(sn);
        notUsed.add(sn);
    }
    /**
     * 获取后四位数SN,随机数
     *
     */
    private static String getSN() {
        String sn = null;
        if (notUsed.size() == 0) {
            throw new RuntimeException("ssrc已经用完");
        } else if (notUsed.size() == 1) {
            sn = notUsed.get(0);
        } else {
            sn = notUsed.get(new Random().nextInt(notUsed.size() - 1));
        }
        notUsed.remove(0);
        isUsed.add(sn);
        return sn;
    }
    private static String getSsrcPrefix() {
        if (ssrcPrefix == null) {
            init();
        }
        return ssrcPrefix;
    }
}
src/main/java/com/genersoft/iot/vmp/utils/SpringBeanFactory.java
New file
@@ -0,0 +1,47 @@
package com.genersoft.iot.vmp.utils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
 * @Description:spring bean获取工厂,获取spring中的已初始化的bean
 * @author: songww
 * @date:   2019年6月25日 下午4:51:52
 *
 */
@Component
public class SpringBeanFactory implements ApplicationContextAware {
    // Spring应用上下文环境
    private static ApplicationContext applicationContext;
    /**
     * 实现ApplicationContextAware接口的回调方法,设置上下文环境
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        SpringBeanFactory.applicationContext = applicationContext;
    }
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }
    /**
     * 获取对象 这里重写了bean方法,起主要作用
     */
    public static Object getBean(String beanId) throws BeansException {
        return applicationContext.getBean(beanId);
    }
    /**
     * 获取当前环境
     */
    public static String getActiveProfile() {
        return applicationContext.getEnvironment().getActiveProfiles()[0];
    }
}
src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceController.java
@@ -1,8 +1,6 @@
package com.genersoft.iot.vmp.vmanager.device;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
src/main/java/com/genersoft/iot/vmp/vmanager/playback/PlaybackController.java
New file
@@ -0,0 +1,47 @@
package com.genersoft.iot.vmp.vmanager.playback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
@RestController
@RequestMapping("/api")
public class PlaybackController {
    private final static Logger logger = LoggerFactory.getLogger(PlaybackController.class);
    @Autowired
    private SIPCommander cmder;
    @Autowired
    private IVideoManagerStorager storager;
    @GetMapping("/playback/{deviceId}/{channelId}")
    public ResponseEntity<String> play(@PathVariable String deviceId,@PathVariable String channelId, String startTime,  String endTime){
        Device device = storager.queryVideoDevice(deviceId);
        String ssrc = cmder.playStreamCmd(device, channelId);
        if (logger.isDebugEnabled()) {
            logger.debug(String.format("设备预览 API调用,deviceId:%s ,channelId:%s",deviceId, channelId));
            logger.debug("设备预览 API调用,ssrc:"+ssrc+",ZLMedia streamId:"+Integer.toHexString(Integer.parseInt(ssrc)));
        }
        if(ssrc!=null) {
            return new ResponseEntity<String>(ssrc,HttpStatus.OK);
        } else {
            logger.warn("设备预览API调用失败!");
            return new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
}
src/main/java/com/genersoft/iot/vmp/vmanager/ptz/PtzController.java
@@ -5,8 +5,8 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -37,7 +37,7 @@
     * @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()) {
src/main/java/com/genersoft/iot/vmp/vmanager/record/RecordController.java
@@ -31,17 +31,18 @@
    @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;
    }
}
src/main/resources/application.yml
@@ -1,10 +1,13 @@
spring:
    application:
        name: wvp
        # 数据存储方式,暂只支持redis,后续支持jdbc
        name: iot-vmp-vmanager
        # 影子数据存储方式,支持redis、jdbc
        database: redis
        # 通信方式,支持kafka、http
        communicate: http
    redis: 
        # Redis服务器IP
        #host: 10.24.20.63
        host: 127.0.0.1
        #端口号
        port: 6379
@@ -13,24 +16,49 @@
        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