Переглянути джерело

dialog去除以及异常情况处理优化

648540858 3 роки тому
батько
коміт
cd117ed228
53 змінених файлів з 3136 додано та 3083 видалено
  1. 6 1
      src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java
  2. 51 0
      src/main/java/com/genersoft/iot/vmp/conf/exception/SsrcTransactionNotFoundException.java
  3. 1 3
      src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java
  4. 27 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/CmdSendFailEvent.java
  5. 8 2
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceNotFoundEvent.java
  6. 6 5
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/SipTransactionInfo.java
  7. 11 18
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/SsrcTransaction.java
  8. 0 5
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeInfo.java
  9. 3 10
      src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java
  10. 1 0
      src/main/java/com/genersoft/iot/vmp/gb28181/event/device/RequestTimeoutEventImpl.java
  11. 27 4
      src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java
  12. 7 51
      src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java
  13. 40 32
      src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/CatalogSubscribeTask.java
  14. 39 32
      src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/MobilePositionSubscribeTask.java
  15. 56 43
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
  16. 19 16
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java
  17. 74 42
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java
  18. 1432 1775
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
  19. 226 390
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
  20. 9 8
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorParent.java
  21. 10 3
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
  22. 23 13
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java
  23. 5 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
  24. 3 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/info/InfoRequestProcessor.java
  25. 3 3
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageRequestProcessor.java
  26. 48 54
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/cmd/DeviceControlQueryMessageHandler.java
  27. 12 2
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java
  28. 7 7
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/DeviceInfoQueryMessageHandler.java
  29. 7 7
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/DeviceStatusQueryMessageHandler.java
  30. 29 37
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/RecordInfoQueryMessageHandler.java
  31. 62 78
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java
  32. 19 26
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/PresetQueryResponseMessageHandler.java
  33. 32 25
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java
  34. 8 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/RegisterResponseProcessor.java
  35. 43 0
      src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java
  36. 42 7
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
  37. 11 12
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java
  38. 36 7
      src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java
  39. 75 43
      src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java
  40. 209 159
      src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
  41. 18 4
      src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisAlarmMsgListener.java
  42. 16 7
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/MobilePosition/MobilePositionController.java
  43. 20 2
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/alarm/AlarmController.java
  44. 31 16
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceConfig.java
  45. 88 55
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceControl.java
  46. 28 15
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java
  47. 40 16
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/platform/PlatformController.java
  48. 33 13
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java
  49. 34 6
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/PlaybackController.java
  50. 29 9
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/ptz/PtzController.java
  51. 36 10
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java
  52. 14 6
      src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiControlController.java
  53. 22 1
      src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiStreamController.java

+ 6 - 1
src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java

@@ -2,6 +2,9 @@ package com.genersoft.iot.vmp.conf;
 
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.utils.DateUtil;
+import com.genersoft.iot.vmp.vmanager.gb28181.device.DeviceQuery;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.util.ObjectUtils;
@@ -15,6 +18,8 @@ import java.util.regex.Pattern;
 @Configuration("mediaConfig")
 public class MediaConfig{
 
+    private final static Logger logger = LoggerFactory.getLogger(MediaConfig.class);
+
     // 修改必须配置,不再支持自动获取
     @Value("${media.id}")
     private String id;
@@ -174,7 +179,7 @@ public class MediaConfig{
                 try {
                     hostAddress = InetAddress.getByName(sdpIp).getHostAddress();
                 } catch (UnknownHostException e) {
-                    throw new RuntimeException(e);
+                    logger.error("[获取SDP IP]: 域名解析失败");
                 }
                 return hostAddress;
             }

+ 51 - 0
src/main/java/com/genersoft/iot/vmp/conf/exception/SsrcTransactionNotFoundException.java

@@ -0,0 +1,51 @@
+package com.genersoft.iot.vmp.conf.exception;
+
+import com.sun.javafx.binding.StringFormatter;
+
+/**
+ * @author lin
+ */
+public class SsrcTransactionNotFoundException extends Exception{
+    private String deviceId;
+    private String channelId;
+    private String callId;
+    private String stream;
+
+
+
+    public SsrcTransactionNotFoundException(String deviceId, String channelId, String callId, String stream) {
+        this.deviceId = deviceId;
+        this.channelId = channelId;
+        this.callId = callId;
+        this.stream = stream;
+    }
+
+    public String getDeviceId() {
+        return deviceId;
+    }
+
+    public String getChannelId() {
+        return channelId;
+    }
+
+    public String getCallId() {
+        return callId;
+    }
+
+    public String getStream() {
+        return stream;
+    }
+
+    @Override
+    public String getMessage() {
+        StringBuffer msg = new StringBuffer();
+        msg.append(StringFormatter.format("缓存事务信息未找到,device:%s channel: %s ",  deviceId, channelId));
+        if (callId != null) {
+            msg.append("callId: " + callId);
+        }
+        if (stream != null) {
+            msg.append("stream: " + stream);
+        }
+        return msg.toString();
+    }
+}

+ 1 - 3
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java

@@ -41,7 +41,7 @@ public class SipLayer{
 	
 	@Bean("sipStack")
 	@DependsOn({"sipFactory"})
-	SipStack createSipStack() throws PeerUnavailableException {
+	SipStackImpl createSipStack() throws PeerUnavailableException {
 		sipStack = ( SipStackImpl )sipFactory.createSipStack(DefaultProperties.getProperties(sipConfig.getMonitorIp(), false));
 		return sipStack;
 	}
@@ -56,7 +56,6 @@ public class SipLayer{
 			tcpSipProvider = (SipProviderImpl)sipStack.createSipProvider(tcpListeningPoint);
 			tcpSipProvider.setDialogErrorsAutomaticallyHandled();
 			tcpSipProvider.addSipListener(sipProcessorObserver);
-//			tcpSipProvider.setAutomaticDialogSupportEnabled(false);
 			logger.info("[Sip Server] TCP 启动成功 {}:{}", sipConfig.getMonitorIp(), sipConfig.getPort());
 		} catch (TransportNotSupportedException e) {
 			e.printStackTrace();
@@ -80,7 +79,6 @@ public class SipLayer{
 			udpListeningPoint = sipStack.createListeningPoint(sipConfig.getMonitorIp(), sipConfig.getPort(), "UDP");
 			udpSipProvider = (SipProviderImpl)sipStack.createSipProvider(udpListeningPoint);
 			udpSipProvider.addSipListener(sipProcessorObserver);
-//			udpSipProvider.setAutomaticDialogSupportEnabled(false);
 		} catch (TransportNotSupportedException e) {
 			e.printStackTrace();
 		} catch (InvalidArgumentException e) {

+ 27 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/CmdSendFailEvent.java

@@ -0,0 +1,27 @@
+package com.genersoft.iot.vmp.gb28181.bean;
+
+import javax.sip.Dialog;
+import java.util.EventObject;
+
+public class CmdSendFailEvent extends EventObject {
+
+    private String callId;
+
+    /**
+     * Constructs a prototypical Event.
+     *
+     * @param dialog
+     * @throws IllegalArgumentException if source is null.
+     */
+    public CmdSendFailEvent(Dialog dialog) {
+        super(dialog);
+    }
+
+    public String getCallId() {
+        return callId;
+    }
+
+    public void setCallId(String callId) {
+        this.callId = callId;
+    }
+}

+ 8 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceNotFoundEvent.java

@@ -4,6 +4,9 @@ import javax.sip.Dialog;
 import java.util.EventObject;
 
 public class DeviceNotFoundEvent extends EventObject {
+
+    private String callId;
+
     /**
      * Constructs a prototypical Event.
      *
@@ -14,8 +17,11 @@ public class DeviceNotFoundEvent extends EventObject {
         super(dialog);
     }
 
+    public String getCallId() {
+        return callId;
+    }
 
-    public Dialog getDialog() {
-        return (Dialog)super.getSource();
+    public void setCallId(String callId) {
+        this.callId = callId;
     }
 }

+ 6 - 5
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SipTransactionInfo.java

@@ -1,6 +1,7 @@
 package com.genersoft.iot.vmp.gb28181.bean;
 
 import gov.nist.javax.sip.message.SIPRequest;
+import gov.nist.javax.sip.message.SIPResponse;
 
 public class SipTransactionInfo {
 
@@ -9,11 +10,11 @@ public class SipTransactionInfo {
     private String toTag;
     private String viaBranch;
 
-    public SipTransactionInfo(SIPRequest request) {
-        this.callId = request.getCallIdHeader().getCallId();
-        this.fromTag = request.getFromTag();
-        this.toTag = request.getToTag();
-        this.viaBranch = request.getTopmostViaHeader().getBranch();
+    public SipTransactionInfo(SIPResponse response) {
+        this.callId = response.getCallIdHeader().getCallId();
+        this.fromTag = response.getFromTag();
+        this.toTag = response.getToTag();
+        this.viaBranch = response.getTopmostViaHeader().getBranch();
     }
 
     public SipTransactionInfo() {

+ 11 - 18
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SsrcTransaction.java

@@ -8,10 +8,11 @@ public class SsrcTransaction {
     private String channelId;
     private String callId;
     private String stream;
-    private byte[] transaction;
-    private byte[] dialog;
     private String mediaServerId;
     private String ssrc;
+
+    private SipTransactionInfo sipTransactionInfo;
+
     private VideoStreamSessionManager.SessionType type;
 
     public String getDeviceId() {
@@ -46,22 +47,6 @@ public class SsrcTransaction {
         this.stream = stream;
     }
 
-    public byte[] getTransaction() {
-        return transaction;
-    }
-
-    public void setTransaction(byte[] transaction) {
-        this.transaction = transaction;
-    }
-
-    public byte[] getDialog() {
-        return dialog;
-    }
-
-    public void setDialog(byte[] dialog) {
-        this.dialog = dialog;
-    }
-
     public String getMediaServerId() {
         return mediaServerId;
     }
@@ -85,4 +70,12 @@ public class SsrcTransaction {
     public void setType(VideoStreamSessionManager.SessionType type) {
         this.type = type;
     }
+
+    public SipTransactionInfo getSipTransactionInfo() {
+        return sipTransactionInfo;
+    }
+
+    public void setSipTransactionInfo(SipTransactionInfo sipTransactionInfo) {
+        this.sipTransactionInfo = sipTransactionInfo;
+    }
 }

+ 0 - 5
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeInfo.java

@@ -1,15 +1,10 @@
 package com.genersoft.iot.vmp.gb28181.bean;
 
-import com.genersoft.iot.vmp.utils.SerializeUtils;
 import gov.nist.javax.sip.message.SIPRequest;
 import gov.nist.javax.sip.message.SIPResponse;
 
-import javax.sip.ClientTransaction;
-import javax.sip.Dialog;
-import javax.sip.RequestEvent;
 import javax.sip.ServerTransaction;
 import javax.sip.header.*;
-import javax.sip.message.Request;
 
 public class SubscribeInfo {
 

+ 3 - 10
src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java

@@ -10,6 +10,7 @@ import org.springframework.stereotype.Component;
 import javax.sip.*;
 import javax.sip.header.CallIdHeader;
 import javax.sip.message.Response;
+import java.text.ParseException;
 import java.time.Instant;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
@@ -56,8 +57,7 @@ public class SipSubscribe {
         logger.debug("errorSubscribes.size:{}",errorSubscribes.size());
     }
 
-    public interface Event {
-        void response(EventResult eventResult);
+    public interface Event { void response(EventResult eventResult) ;
     }
 
     /**
@@ -81,18 +81,13 @@ public class SipSubscribe {
         public EventResultType type;
         public String msg;
         public String callId;
-        public Dialog dialog;
         public EventObject event;
 
-        public EventResult() {
-        }
-
         public EventResult(EventObject event) {
             this.event = event;
             if (event instanceof ResponseEvent) {
                 ResponseEvent responseEvent = (ResponseEvent)event;
                 Response response = responseEvent.getResponse();
-                this.dialog = responseEvent.getDialog();
                 this.type = EventResultType.response;
                 if (response != null) {
                     this.msg = response.getReasonPhrase();
@@ -127,12 +122,10 @@ public class SipSubscribe {
                 this.statusCode = -1024;
                 this.callId = dialogTerminatedEvent.getDialog().getCallId().getCallId();
             }else if (event instanceof DeviceNotFoundEvent) {
-                DeviceNotFoundEvent deviceNotFoundEvent = (DeviceNotFoundEvent)event;
                 this.type = EventResultType.deviceNotFoundEvent;
                 this.msg = "设备未找到";
                 this.statusCode = -1024;
-                this.dialog = deviceNotFoundEvent.getDialog();
-                this.callId = this.dialog != null ?deviceNotFoundEvent.getDialog().getCallId().getCallId() : null;
+                this.callId = ((DeviceNotFoundEvent) event).getCallId();
             }
         }
     }

+ 1 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/event/device/RequestTimeoutEventImpl.java

@@ -36,6 +36,7 @@ public class RequestTimeoutEventImpl implements ApplicationListener<RequestTimeo
                 }
                 deviceService.offline(device.getDeviceId());
             }
+
         }
     }
 }

+ 27 - 4
src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java

@@ -18,6 +18,9 @@ import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
 import org.springframework.util.StringUtils;
 
+import javax.sip.InvalidArgumentException;
+import javax.sip.SipException;
+import java.text.ParseException;
 import java.util.*;
 
 /**
@@ -96,7 +99,12 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
                     }
                     if (deviceChannelList.size() > 0) {
                         logger.info("[Catalog事件: {}]平台:{},影响通道{}个", event.getType(), event.getPlatformId(), deviceChannelList.size());
-                        sipCommanderFroPlatform.sendNotifyForCatalogOther(event.getType(), parentPlatform, deviceChannelList, subscribe, null);
+                        try {
+                            sipCommanderFroPlatform.sendNotifyForCatalogOther(event.getType(), parentPlatform, deviceChannelList, subscribe, null);
+                        } catch (InvalidArgumentException | ParseException | NoSuchFieldException | SipException |
+                                 IllegalAccessException e) {
+                            logger.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage());
+                        }
                     }
                 }else if (parentPlatformMap.keySet().size() > 0) {
                     for (String gbId : parentPlatformMap.keySet()) {
@@ -112,7 +120,12 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
                                 DeviceChannel deviceChannel = new DeviceChannel();
                                 deviceChannel.setChannelId(gbId);
                                 deviceChannelList.add(deviceChannel);
-                                sipCommanderFroPlatform.sendNotifyForCatalogOther(event.getType(), platform, deviceChannelList, subscribeInfo, null);
+                                try {
+                                    sipCommanderFroPlatform.sendNotifyForCatalogOther(event.getType(), platform, deviceChannelList, subscribeInfo, null);
+                                } catch (InvalidArgumentException | ParseException | NoSuchFieldException | SipException |
+                                         IllegalAccessException e) {
+                                    logger.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage());
+                                }
                             }
                         }
                     }
@@ -137,7 +150,12 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
                     }
                     if (deviceChannelList.size() > 0) {
                         logger.info("[Catalog事件: {}]平台:{},影响通道{}个", event.getType(), event.getPlatformId(), deviceChannelList.size());
-                        sipCommanderFroPlatform.sendNotifyForCatalogAddOrUpdate(event.getType(), parentPlatform, deviceChannelList, subscribe, null);
+                        try {
+                            sipCommanderFroPlatform.sendNotifyForCatalogAddOrUpdate(event.getType(), parentPlatform, deviceChannelList, subscribe, null);
+                        } catch (InvalidArgumentException | ParseException | NoSuchFieldException | SipException |
+                                 IllegalAccessException e) {
+                            logger.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage());
+                        }
                     }
                 }else if (parentPlatformMap.keySet().size() > 0) {
                     for (String gbId : parentPlatformMap.keySet()) {
@@ -157,7 +175,12 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
                                     DeviceChannel deviceChannelByStream = gbStreamService.getDeviceChannelListByStreamWithStatus(gbStream, gbStream.getCatalogId(), platform);
                                     deviceChannelList.add(deviceChannelByStream);
                                 }
-                                sipCommanderFroPlatform.sendNotifyForCatalogAddOrUpdate(event.getType(), platform, deviceChannelList, subscribeInfo, null);
+                                try {
+                                    sipCommanderFroPlatform.sendNotifyForCatalogAddOrUpdate(event.getType(), platform, deviceChannelList, subscribeInfo, null);
+                                } catch (InvalidArgumentException | ParseException | NoSuchFieldException |
+                                         SipException | IllegalAccessException e) {
+                                    logger.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage());
+                                }
                             }
                         }
                     }

+ 7 - 51
src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java

@@ -8,9 +8,13 @@ import javax.sip.Dialog;
 
 import com.genersoft.iot.vmp.common.VideoManagerConstants;
 import com.genersoft.iot.vmp.conf.UserSetting;
+import com.genersoft.iot.vmp.gb28181.bean.SipMsgInfo;
+import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
 import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
 import com.genersoft.iot.vmp.utils.SerializeUtils;
 import com.genersoft.iot.vmp.utils.redis.RedisUtil;
+import gov.nist.javax.sip.message.SIPRequest;
+import gov.nist.javax.sip.message.SIPResponse;
 import gov.nist.javax.sip.stack.SIPDialog;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
@@ -42,15 +46,14 @@ public class VideoStreamSessionManager {
 	 * @param callId 一次请求的CallID
 	 * @param stream 流名称
 	 * @param mediaServerId 所使用的流媒体ID
-	 * @param transaction 事务
+	 * @param response 回复
 	 */
-	public void put(String deviceId, String channelId, String callId, String stream, String ssrc, String mediaServerId, ClientTransaction transaction, SessionType type){
+	public void put(String deviceId, String channelId, String callId, String stream, String ssrc, String mediaServerId, SIPResponse response, SessionType type){
 		SsrcTransaction ssrcTransaction = new SsrcTransaction();
 		ssrcTransaction.setDeviceId(deviceId);
 		ssrcTransaction.setChannelId(channelId);
 		ssrcTransaction.setStream(stream);
-		byte[] transactionByteArray = SerializeUtils.serialize(transaction);
-		ssrcTransaction.setTransaction(transactionByteArray);
+		ssrcTransaction.setSipTransactionInfo(new SipTransactionInfo(response));
 		ssrcTransaction.setCallId(callId);
 		ssrcTransaction.setSsrc(ssrc);
 		ssrcTransaction.setMediaServerId(mediaServerId);
@@ -62,53 +65,6 @@ public class VideoStreamSessionManager {
 				+ "_" +  deviceId + "_" + channelId + "_" + callId + "_" + stream, ssrcTransaction);
 	}
 
-	public void put(String deviceId, String channelId, String callId, Dialog dialog){
-		SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, callId, null);
-		if (ssrcTransaction != null) {
-			byte[] dialogByteArray = SerializeUtils.serialize(dialog);
-			ssrcTransaction.setDialog(dialogByteArray);
-		}
-		RedisUtil.set(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId()
-				+ "_" +  deviceId + "_" + channelId + "_" + ssrcTransaction.getCallId() + "_"
-				+ ssrcTransaction.getStream(), ssrcTransaction);
-	}
-
-	
-	public ClientTransaction getTransaction(String deviceId, String channelId, String stream, String callId){
-		SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, callId, stream);
-		if (ssrcTransaction == null) {
-			return null;
-		}
-		byte[] transactionByteArray = ssrcTransaction.getTransaction();
-		ClientTransaction clientTransaction = (ClientTransaction)SerializeUtils.deSerialize(transactionByteArray);
-		return clientTransaction;
-	}
-
-	public SIPDialog getDialogByStream(String deviceId, String channelId, String stream){
-		SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, null, stream);
-		if (ssrcTransaction == null) {
-			return null;
-		}
-		byte[] dialogByteArray = ssrcTransaction.getDialog();
-		if (dialogByteArray == null) {
-			return null;
-		}
-		SIPDialog dialog = (SIPDialog)SerializeUtils.deSerialize(dialogByteArray);
-		return dialog;
-	}
-
-	public SIPDialog getDialogByCallId(String deviceId, String channelId, String callId){
-		SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, callId, null);
-		if (ssrcTransaction == null) {
-			return null;
-		}
-		byte[] dialogByteArray = ssrcTransaction.getDialog();
-		if (dialogByteArray == null) {
-			return null;
-		}
-		return (SIPDialog)SerializeUtils.deSerialize(dialogByteArray);
-	}
-
 	public SsrcTransaction getSsrcTransaction(String deviceId, String channelId, String callId, String stream){
 
 		if (ObjectUtils.isEmpty(deviceId)) {

+ 40 - 32
src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/CatalogSubscribeTask.java

@@ -10,9 +10,7 @@ import org.slf4j.LoggerFactory;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 
-import javax.sip.Dialog;
-import javax.sip.DialogState;
-import javax.sip.ResponseEvent;
+import javax.sip.*;
 import javax.sip.header.ToHeader;
 import java.text.ParseException;
 import java.util.Timer;
@@ -44,23 +42,29 @@ public class CatalogSubscribeTask implements ISubscribeTask {
         if (dynamicTask.get(taskKey) != null) {
             dynamicTask.stop(taskKey);
         }
-        SIPRequest sipRequest = sipCommander.catalogSubscribe(device, request, eventResult -> {
-            ResponseEvent event = (ResponseEvent) eventResult.event;
-            // 成功
-            logger.info("[目录订阅]成功: {}", device.getDeviceId());
-            ToHeader toHeader = (ToHeader)event.getResponse().getHeader(ToHeader.NAME);
-            try {
-                this.request.getToHeader().setTag(toHeader.getTag());
-            } catch (ParseException e) {
-                logger.info("[目录订阅]成功: 但为request设置ToTag失败");
+        SIPRequest sipRequest = null;
+        try {
+            sipRequest = sipCommander.catalogSubscribe(device, request, eventResult -> {
+                ResponseEvent event = (ResponseEvent) eventResult.event;
+                // 成功
+                logger.info("[目录订阅]成功: {}", device.getDeviceId());
+                ToHeader toHeader = (ToHeader)event.getResponse().getHeader(ToHeader.NAME);
+                try {
+                    this.request.getToHeader().setTag(toHeader.getTag());
+                } catch (ParseException e) {
+                    logger.info("[目录订阅]成功: 但为request设置ToTag失败");
+                    this.request = null;
+                }
+            },eventResult -> {
                 this.request = null;
-            }
-        },eventResult -> {
-            this.request = null;
-            // 失败
-            logger.warn("[目录订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg);
-            dynamicTask.startDelay(taskKey, CatalogSubscribeTask.this, 2000);
-        });
+                // 失败
+                logger.warn("[目录订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg);
+                dynamicTask.startDelay(taskKey, CatalogSubscribeTask.this, 2000);
+            });
+        } catch (InvalidArgumentException | SipException | ParseException e) {
+            logger.error("[命令发送失败] 目录订阅: {}", e.getMessage());
+
+        }
         if (sipRequest != null) {
             this.request = sipRequest;
         }
@@ -80,18 +84,22 @@ public class CatalogSubscribeTask implements ISubscribeTask {
             dynamicTask.stop(taskKey);
         }
         device.setSubscribeCycleForCatalog(0);
-        sipCommander.catalogSubscribe(device, request, eventResult -> {
-            ResponseEvent event = (ResponseEvent) eventResult.event;
-            if (event.getResponse().getRawContent() != null) {
-                // 成功
-                logger.info("[取消目录订阅订阅]成功: {}", device.getDeviceId());
-            }else {
-                // 成功
-                logger.info("[取消目录订阅订阅]成功: {}", device.getDeviceId());
-            }
-        },eventResult -> {
-            // 失败
-            logger.warn("[取消目录订阅订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg);
-        });
+        try {
+            sipCommander.catalogSubscribe(device, request, eventResult -> {
+                ResponseEvent event = (ResponseEvent) eventResult.event;
+                if (event.getResponse().getRawContent() != null) {
+                    // 成功
+                    logger.info("[取消目录订阅订阅]成功: {}", device.getDeviceId());
+                }else {
+                    // 成功
+                    logger.info("[取消目录订阅订阅]成功: {}", device.getDeviceId());
+                }
+            },eventResult -> {
+                // 失败
+                logger.warn("[取消目录订阅订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg);
+            });
+        } catch (InvalidArgumentException | SipException | ParseException e) {
+            logger.error("[命令发送失败] 取消目录订阅订阅: {}", e.getMessage());
+        }
     }
 }

+ 39 - 32
src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/MobilePositionSubscribeTask.java

@@ -11,9 +11,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.scheduling.annotation.Async;
 
-import javax.sip.Dialog;
-import javax.sip.DialogState;
-import javax.sip.ResponseEvent;
+import javax.sip.*;
 import javax.sip.header.ToHeader;
 import java.text.ParseException;
 import java.util.Timer;
@@ -43,23 +41,28 @@ public class MobilePositionSubscribeTask implements ISubscribeTask {
         if (dynamicTask.get(taskKey) != null) {
             dynamicTask.stop(taskKey);
         }
-        SIPRequest sipRequest = sipCommander.mobilePositionSubscribe(device, request, eventResult -> {
-            // 成功
-            logger.info("[移动位置订阅]成功: {}", device.getDeviceId());
-            ResponseEvent event = (ResponseEvent) eventResult.event;
-            ToHeader toHeader = (ToHeader)event.getResponse().getHeader(ToHeader.NAME);
-            try {
-                this.request.getToHeader().setTag(toHeader.getTag());
-            } catch (ParseException e) {
-                logger.info("[移动位置订阅]成功: 为request设置ToTag失败");
+        SIPRequest sipRequest = null;
+        try {
+            sipRequest = sipCommander.mobilePositionSubscribe(device, request, eventResult -> {
+                // 成功
+                logger.info("[移动位置订阅]成功: {}", device.getDeviceId());
+                ResponseEvent event = (ResponseEvent) eventResult.event;
+                ToHeader toHeader = (ToHeader)event.getResponse().getHeader(ToHeader.NAME);
+                try {
+                    this.request.getToHeader().setTag(toHeader.getTag());
+                } catch (ParseException e) {
+                    logger.info("[移动位置订阅]成功: 为request设置ToTag失败");
+                    this.request = null;
+                }
+            },eventResult -> {
                 this.request = null;
-            }
-        },eventResult -> {
-            this.request = null;
-            // 失败
-            logger.warn("[移动位置订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg);
-            dynamicTask.startDelay(taskKey, MobilePositionSubscribeTask.this, 2000);
-        });
+                // 失败
+                logger.warn("[移动位置订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg);
+                dynamicTask.startDelay(taskKey, MobilePositionSubscribeTask.this, 2000);
+            });
+        } catch (InvalidArgumentException | SipException | ParseException e) {
+            logger.error("[命令发送失败] 移动位置订阅: {}", e.getMessage());
+        }
         if (sipRequest != null) {
             this.request = sipRequest;
         }
@@ -79,18 +82,22 @@ public class MobilePositionSubscribeTask implements ISubscribeTask {
             dynamicTask.stop(taskKey);
         }
         device.setSubscribeCycleForMobilePosition(0);
-        sipCommander.mobilePositionSubscribe(device, request, eventResult -> {
-            ResponseEvent event = (ResponseEvent) eventResult.event;
-            if (event.getResponse().getRawContent() != null) {
-                // 成功
-                logger.info("[取消移动位置订阅]成功: {}", device.getDeviceId());
-            }else {
-                // 成功
-                logger.info("[取消移动位置订阅]成功: {}", device.getDeviceId());
-            }
-        },eventResult -> {
-            // 失败
-            logger.warn("[取消移动位置订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg);
-        });
+        try {
+            sipCommander.mobilePositionSubscribe(device, request, eventResult -> {
+                ResponseEvent event = (ResponseEvent) eventResult.event;
+                if (event.getResponse().getRawContent() != null) {
+                    // 成功
+                    logger.info("[取消移动位置订阅]成功: {}", device.getDeviceId());
+                }else {
+                    // 成功
+                    logger.info("[取消移动位置订阅]成功: {}", device.getDeviceId());
+                }
+            },eventResult -> {
+                // 失败
+                logger.warn("[取消移动位置订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg);
+            });
+        } catch (InvalidArgumentException | SipException | ParseException e) {
+            logger.error("[命令发送失败] 取消移动位置订阅: {}", e.getMessage());
+        }
     }
 }

+ 56 - 43
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java

@@ -1,6 +1,7 @@
 package com.genersoft.iot.vmp.gb28181.transmit.cmd;
 
 import com.genersoft.iot.vmp.common.StreamInfo;
+import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
 import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
@@ -9,6 +10,11 @@ import com.genersoft.iot.vmp.service.bean.SSRCInfo;
 import gov.nist.javax.sip.message.SIPRequest;
 
 import javax.sip.Dialog;
+import javax.sip.InvalidArgumentException;
+import javax.sip.PeerUnavailableException;
+import javax.sip.SipException;
+import javax.sip.message.Request;
+import java.text.ParseException;
 
 /**    
  * @description:设备能力接口,用于定义设备的控制、查询能力   
@@ -25,7 +31,7 @@ public interface ISIPCommander {
 	 * @param leftRight  镜头左移右移 0:停止 1:左移 2:右移
      * @param upDown     镜头上移下移 0:停止 1:上移 2:下移
 	 */
-	boolean ptzdirectCmd(Device device,String channelId,int leftRight, int upDown);
+	void ptzdirectCmd(Device device,String channelId,int leftRight, int upDown) throws InvalidArgumentException, ParseException, SipException;
 	
 	/**
 	 * 云台方向放控制
@@ -36,7 +42,7 @@ public interface ISIPCommander {
      * @param upDown     镜头上移下移 0:停止 1:上移 2:下移
      * @param moveSpeed  镜头移动速度
 	 */
-	boolean ptzdirectCmd(Device device,String channelId,int leftRight, int upDown, int moveSpeed);
+	void ptzdirectCmd(Device device,String channelId,int leftRight, int upDown, int moveSpeed) throws InvalidArgumentException, ParseException, SipException;
 	
 	/**
 	 * 云台缩放控制,使用配置文件中的默认镜头缩放速度
@@ -45,7 +51,7 @@ public interface ISIPCommander {
 	 * @param channelId  预览通道
      * @param inOut      镜头放大缩小 0:停止 1:缩小 2:放大
 	 */
-	boolean ptzZoomCmd(Device device,String channelId,int inOut);
+	void ptzZoomCmd(Device device,String channelId,int inOut) throws InvalidArgumentException, ParseException, SipException;
 	
 	/**
 	 * 云台缩放控制
@@ -54,7 +60,7 @@ public interface ISIPCommander {
 	 * @param channelId  预览通道
      * @param inOut      镜头放大缩小 0:停止 1:缩小 2:放大
 	 */
-	boolean ptzZoomCmd(Device device,String channelId,int inOut, int moveSpeed);
+	void ptzZoomCmd(Device device,String channelId,int inOut, int moveSpeed) throws InvalidArgumentException, ParseException, SipException;
 	
 	/**
 	 * 云台控制,支持方向与缩放控制
@@ -67,7 +73,7 @@ public interface ISIPCommander {
      * @param moveSpeed  镜头移动速度
      * @param zoomSpeed  镜头缩放速度
 	 */
-	boolean ptzCmd(Device device,String channelId,int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed);
+	void ptzCmd(Device device,String channelId,int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed) throws InvalidArgumentException, SipException, ParseException;
 	
 	/**
 	 * 前端控制,包括PTZ指令、FI指令、预置位指令、巡航指令、扫描指令和辅助开关指令
@@ -79,7 +85,7 @@ public interface ISIPCommander {
      * @param parameter2	数据2
      * @param combineCode2	组合码2
 	 */
-	boolean frontEndCmd(Device device, String channelId, int cmdCode, int parameter1, int parameter2, int combineCode2);
+	void frontEndCmd(Device device, String channelId, int cmdCode, int parameter1, int parameter2, int combineCode2) throws SipException, InvalidArgumentException, ParseException;
 	
 	/**
 	 * 前端控制指令(用于转发上级指令)
@@ -87,14 +93,14 @@ public interface ISIPCommander {
 	 * @param channelId		预览通道
 	 * @param cmdString		前端控制指令串
 	 */
-	boolean fronEndCmd(Device device, String channelId, String cmdString, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent);
+	void fronEndCmd(Device device, String channelId, String cmdString, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
 
 	/**
 	 * 请求预览视频流
 	 * @param device  视频设备
 	 * @param channelId  预览通道
 	 */
-	void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent);
+	void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
 
 	/**
 	 * 请求回放视频流
@@ -104,7 +110,7 @@ public interface ISIPCommander {
 	 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
 	 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
 	 */
-	void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInf, Device device, String channelId, String startTime, String endTime,InviteStreamCallback inviteStreamCallback, InviteStreamCallback event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent);
+	void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInf, Device device, String channelId, String startTime, String endTime,InviteStreamCallback inviteStreamCallback, InviteStreamCallback event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
 
 	/**
 	 * 请求历史媒体下载
@@ -117,33 +123,33 @@ public interface ISIPCommander {
 	 */ 
 	void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
 						   String startTime, String endTime, int downloadSpeed, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
-						   SipSubscribe.Event errorEvent);
+						   SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
 
 	/**
 	 * 视频流停止
 	 */
-	void streamByeCmd(String deviceId, String channelId, String stream, String callId, SipSubscribe.Event okEvent);
-	void streamByeCmd(String deviceId, String channelId, String stream, String callId);
+	void streamByeCmd(Device device, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException;
+	void streamByeCmd(Device device, String channelId, String stream, String callId) throws InvalidArgumentException, ParseException, SipException, SsrcTransactionNotFoundException;
 
 	/**
 	 * 回放暂停
 	 */
-	void playPauseCmd(Device device, StreamInfo streamInfo);
+	void playPauseCmd(Device device, StreamInfo streamInfo) throws InvalidArgumentException, ParseException, SipException;
 
 	/**
 	 * 回放恢复
 	 */
-	void playResumeCmd(Device device, StreamInfo streamInfo);
+	void playResumeCmd(Device device, StreamInfo streamInfo) throws InvalidArgumentException, ParseException, SipException;
 
 	/**
 	 * 回放拖动播放
 	 */
-	void playSeekCmd(Device device, StreamInfo streamInfo, long seekTime);
+	void playSeekCmd(Device device, StreamInfo streamInfo, long seekTime) throws InvalidArgumentException, ParseException, SipException;
 
 	/**
 	 * 回放倍速播放
 	 */
-	void playSpeedCmd(Device device, StreamInfo streamInfo, Double speed);
+	void playSpeedCmd(Device device, StreamInfo streamInfo, Double speed) throws InvalidArgumentException, ParseException, SipException;
 	
 	/**
 	 * 回放控制
@@ -151,23 +157,24 @@ public interface ISIPCommander {
 	 * @param streamInfo
 	 * @param content
 	 */
-	void playbackControlCmd(Device device, StreamInfo streamInfo, String content,SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent);
+	void playbackControlCmd(Device device, StreamInfo streamInfo, String content,SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException;
 
-	/**
+
+    /**
 	 * 语音广播
 	 * 
 	 * @param device  视频设备
 	 * @param channelId  预览通道
 	 */
-	boolean audioBroadcastCmd(Device device,String channelId);
+	void audioBroadcastCmd(Device device,String channelId);
 	
 	/**
 	 * 语音广播
 	 * 
 	 * @param device  视频设备
 	 */
-	void audioBroadcastCmd(Device device, SipSubscribe.Event okEvent);
-	boolean audioBroadcastCmd(Device device);
+	void audioBroadcastCmd(Device device, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
+	void audioBroadcastCmd(Device device) throws InvalidArgumentException, SipException, ParseException;
 	
 	/**
 	 * 音视频录像控制
@@ -176,21 +183,21 @@ public interface ISIPCommander {
 	 * @param channelId  	预览通道
 	 * @param recordCmdStr	录像命令:Record / StopRecord
 	 */
-	boolean recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent);
+	void recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
 	
 	/**
 	 * 远程启动控制命令
 	 * 
 	 * @param device	视频设备
 	 */
-	boolean teleBootCmd(Device device);
+	void teleBootCmd(Device device) throws InvalidArgumentException, SipException, ParseException;
 
 	/**
 	 * 报警布防/撤防命令
 	 * 
 	 * @param device  	视频设备
 	 */
-	boolean guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent);
+	void guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
 	
 	/**
 	 * 报警复位命令
@@ -199,7 +206,7 @@ public interface ISIPCommander {
 	 * @param alarmMethod	报警方式(可选)
 	 * @param alarmType		报警类型(可选)
 	 */
-	boolean alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent);
+	void alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
 	
 	/**
 	 * 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧
@@ -207,7 +214,7 @@ public interface ISIPCommander {
 	 * @param device  视频设备
 	 * @param channelId  预览通道
 	 */
-	boolean iFrameCmd(Device device, String channelId);
+	void iFrameCmd(Device device, String channelId) throws InvalidArgumentException, SipException, ParseException;
 	
 	/**
 	 * 看守位控制命令
@@ -217,14 +224,14 @@ public interface ISIPCommander {
 	 * @param resetTime		自动归位时间间隔,开启看守位时使用,单位:秒(s)
 	 * @param presetIndex	调用预置位编号,开启看守位时使用,取值范围0~255
 	 */
-	boolean homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent);
+	void homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
 	
 	/**
 	 * 设备配置命令
 	 * 
 	 * @param device  视频设备
 	 */
-	boolean deviceConfigCmd(Device device);
+	void deviceConfigCmd(Device device);
 	
 		/**
 	 * 设备配置命令:basicParam
@@ -236,14 +243,14 @@ public interface ISIPCommander {
 	 * @param heartBeatInterval	心跳间隔时间(可选)
 	 * @param heartBeatCount	心跳超时次数(可选)
 	 */  
-	boolean deviceBasicConfigCmd(Device device, String channelId, String name, String expiration, String heartBeatInterval, String heartBeatCount, SipSubscribe.Event errorEvent);
+	void deviceBasicConfigCmd(Device device, String channelId, String name, String expiration, String heartBeatInterval, String heartBeatCount, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
 	
 	/**
 	 * 查询设备状态
 	 * 
 	 * @param device 视频设备
 	 */
-	boolean deviceStatusQuery(Device device, SipSubscribe.Event errorEvent);
+	void deviceStatusQuery(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
 	
 	/**
 	 * 查询设备信息
@@ -251,14 +258,14 @@ public interface ISIPCommander {
 	 * @param device 视频设备
 	 * @return 
 	 */
-	boolean deviceInfoQuery(Device device);
+	void deviceInfoQuery(Device device) throws InvalidArgumentException, SipException, ParseException;
 	
 	/**
 	 * 查询目录列表
 	 * 
 	 * @param device 视频设备
 	 */
-	boolean catalogQuery(Device device, int sn, SipSubscribe.Event errorEvent);
+	void catalogQuery(Device device, int sn, SipSubscribe.Event errorEvent) throws SipException, InvalidArgumentException, ParseException;
 	
 	/**
 	 * 查询录像信息
@@ -268,7 +275,7 @@ public interface ISIPCommander {
 	 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
 	 * @param sn
 	 */
-	boolean recordInfoQuery(Device device, String channelId, String startTime, String endTime, int sn,  Integer Secrecy, String type, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent);
+	void recordInfoQuery(Device device, String channelId, String startTime, String endTime, int sn,  Integer Secrecy, String type, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
 	
 	/**
 	 * 查询报警信息
@@ -282,8 +289,8 @@ public interface ISIPCommander {
 	 * @param endTime		报警发生终止时间(可选)
 	 * @return				true = 命令发送成功
 	 */
-	boolean alarmInfoQuery(Device device, String startPriority, String endPriority, String alarmMethod,
-							String alarmType, String startTime, String endTime, SipSubscribe.Event errorEvent);	
+	void alarmInfoQuery(Device device, String startPriority, String endPriority, String alarmMethod,
+							String alarmType, String startTime, String endTime, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
 	
 	/**
 	 * 查询设备配置
@@ -292,21 +299,21 @@ public interface ISIPCommander {
 	 * @param channelId		通道编码(可选)
 	 * @param configType	配置类型:
 	 */
-	boolean deviceConfigQuery(Device device, String channelId, String configType,  SipSubscribe.Event errorEvent);
+	void deviceConfigQuery(Device device, String channelId, String configType,  SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
 	
 	/**
 	 * 查询设备预置位置
 	 * 
 	 * @param device 视频设备
 	 */
-	boolean presetQuery(Device device, String channelId, SipSubscribe.Event errorEvent);
+	void presetQuery(Device device, String channelId, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
 	
 	/**
 	 * 查询移动设备位置数据
 	 * 
 	 * @param device 视频设备
 	 */
-	boolean mobilePostitionQuery(Device device, SipSubscribe.Event errorEvent);
+	void mobilePostitionQuery(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
 
 	/**
 	 * 订阅、取消订阅移动位置
@@ -314,7 +321,7 @@ public interface ISIPCommander {
 	 * @param device	视频设备
 	 * @return			true = 命令发送成功
 	 */
-	SIPRequest mobilePositionSubscribe(Device device, SIPRequest request, SipSubscribe.Event okEvent , SipSubscribe.Event errorEvent);
+	SIPRequest mobilePositionSubscribe(Device device, SIPRequest request, SipSubscribe.Event okEvent , SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
 
 	/**
 	 * 订阅、取消订阅报警信息
@@ -327,14 +334,14 @@ public interface ISIPCommander {
 	 * @param endTime		报警发生终止时间(可选)
 	 * @return				true = 命令发送成功
 	 */
-	boolean alarmSubscribe(Device device, int expires, String startPriority, String endPriority, String alarmMethod, String alarmType, String startTime, String endTime);
+	void alarmSubscribe(Device device, int expires, String startPriority, String endPriority, String alarmMethod, String alarmType, String startTime, String endTime) throws InvalidArgumentException, SipException, ParseException;
 
 	/**
 	 * 订阅、取消订阅目录信息
 	 * @param device		视频设备
 	 * @return				true = 命令发送成功
 	 */
-	SIPRequest catalogSubscribe(Device device, SIPRequest request, SipSubscribe.Event okEvent ,SipSubscribe.Event errorEvent);
+	SIPRequest catalogSubscribe(Device device, SIPRequest request, SipSubscribe.Event okEvent ,SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
 
 	/**
 	 * 拉框控制命令
@@ -343,7 +350,7 @@ public interface ISIPCommander {
 	 * @param channelId 通道id
 	 * @param cmdString 前端控制指令串
 	 */
-	boolean dragZoomCmd(Device device, String channelId, String cmdString);
+	void dragZoomCmd(Device device, String channelId, String cmdString) throws InvalidArgumentException, SipException, ParseException;
 
 
 	/**
@@ -352,5 +359,11 @@ public interface ISIPCommander {
 	 * @param deviceAlarm 报警信息信息
 	 * @return
 	 */
-	boolean sendAlarmMessage(Device device, DeviceAlarm deviceAlarm);
+	void sendAlarmMessage(Device device, DeviceAlarm deviceAlarm) throws InvalidArgumentException, SipException, ParseException;
+
+	void transmitRequest(String transport, Request request) throws SipException, ParseException ;
+
+	void transmitRequest(String transport, Request request, SipSubscribe.Event errorEvent) throws SipException, ParseException;
+
+	void transmitRequest(String transport, Request request, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws SipException, ParseException;
 }

+ 19 - 16
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java

@@ -4,7 +4,10 @@ import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
 import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
 
+import javax.sip.InvalidArgumentException;
+import javax.sip.SipException;
 import javax.sip.header.WWWAuthenticateHeader;
+import java.text.ParseException;
 import java.util.List;
 
 public interface ISIPCommanderForPlatform {
@@ -14,15 +17,15 @@ public interface ISIPCommanderForPlatform {
      * @param parentPlatform
      * @return
      */
-    boolean register(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent);
-    boolean register(ParentPlatform parentPlatform, String callId, WWWAuthenticateHeader www, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent, boolean registerAgain, boolean isRegister);
+    void register(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException;
+    void register(ParentPlatform parentPlatform, String callId, WWWAuthenticateHeader www, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent, boolean registerAgain, boolean isRegister) throws SipException, InvalidArgumentException, ParseException;
 
     /**
      * 向上级平台注销
      * @param parentPlatform
      * @return
      */
-    boolean unregister(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent);
+    void unregister(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException;
 
 
     /**
@@ -30,7 +33,7 @@ public interface ISIPCommanderForPlatform {
      * @param parentPlatform
      * @return callId(作为接受回复的判定)
      */
-    String keepalive(ParentPlatform parentPlatform,SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent);
+    String keepalive(ParentPlatform parentPlatform,SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException;
 
 
     /**
@@ -42,8 +45,8 @@ public interface ISIPCommanderForPlatform {
      * @param size
      * @return
      */
-    boolean catalogQuery(DeviceChannel channel, ParentPlatform parentPlatform, String sn, String fromTag, int size);
-    boolean catalogQuery(List<DeviceChannel> channels, ParentPlatform parentPlatform, String sn, String fromTag);
+    void catalogQuery(DeviceChannel channel, ParentPlatform parentPlatform, String sn, String fromTag, int size) throws SipException, InvalidArgumentException, ParseException;
+    void catalogQuery(List<DeviceChannel> channels, ParentPlatform parentPlatform, String sn, String fromTag) throws InvalidArgumentException, ParseException, SipException;
 
     /**
      * 向上级回复DeviceInfo查询信息
@@ -52,7 +55,7 @@ public interface ISIPCommanderForPlatform {
      * @param fromTag
      * @return
      */
-    boolean deviceInfoResponse(ParentPlatform parentPlatform, String sn, String fromTag);
+    void deviceInfoResponse(ParentPlatform parentPlatform, String sn, String fromTag) throws SipException, InvalidArgumentException, ParseException;
 
     /**
      * 向上级回复DeviceStatus查询信息
@@ -61,7 +64,7 @@ public interface ISIPCommanderForPlatform {
      * @param fromTag
      * @return
      */
-    boolean deviceStatusResponse(ParentPlatform parentPlatform, String sn, String fromTag);
+    void deviceStatusResponse(ParentPlatform parentPlatform, String sn, String fromTag) throws SipException, InvalidArgumentException, ParseException;
 
     /**
      * 向上级回复移动位置订阅消息
@@ -70,7 +73,7 @@ public interface ISIPCommanderForPlatform {
      * @param subscribeInfo 订阅相关的信息
      * @return
      */
-    boolean sendNotifyMobilePosition(ParentPlatform parentPlatform, GPSMsgInfo gpsMsgInfo, SubscribeInfo subscribeInfo);
+    void sendNotifyMobilePosition(ParentPlatform parentPlatform, GPSMsgInfo gpsMsgInfo, SubscribeInfo subscribeInfo) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException;
 
     /**
      * 向上级回复报警消息
@@ -78,21 +81,21 @@ public interface ISIPCommanderForPlatform {
      * @param deviceAlarm 报警信息信息
      * @return
      */
-    boolean sendAlarmMessage(ParentPlatform parentPlatform, DeviceAlarm deviceAlarm);
+    void sendAlarmMessage(ParentPlatform parentPlatform, DeviceAlarm deviceAlarm) throws SipException, InvalidArgumentException, ParseException;
 
     /**
      * 回复catalog事件-增加/更新
      * @param parentPlatform
      * @param deviceChannels
      */
-    boolean sendNotifyForCatalogAddOrUpdate(String type, ParentPlatform parentPlatform, List<DeviceChannel> deviceChannels, SubscribeInfo subscribeInfo, Integer index);
+    void sendNotifyForCatalogAddOrUpdate(String type, ParentPlatform parentPlatform, List<DeviceChannel> deviceChannels, SubscribeInfo subscribeInfo, Integer index) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException;
 
     /**
      * 回复catalog事件-删除
      * @param parentPlatform
      * @param deviceChannels
      */
-    boolean sendNotifyForCatalogOther(String type, ParentPlatform parentPlatform, List<DeviceChannel> deviceChannels, SubscribeInfo subscribeInfo, Integer index);
+    void sendNotifyForCatalogOther(String type, ParentPlatform parentPlatform, List<DeviceChannel> deviceChannels, SubscribeInfo subscribeInfo, Integer index) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException;
 
     /**
      * 回复recordInfo
@@ -101,7 +104,7 @@ public interface ISIPCommanderForPlatform {
      * @param fromTag fromTag
      * @param recordInfo 录像信息
      */
-    boolean recordInfo(DeviceChannel deviceChannel, ParentPlatform parentPlatform, String fromTag, RecordInfo recordInfo);
+    void recordInfo(DeviceChannel deviceChannel, ParentPlatform parentPlatform, String fromTag, RecordInfo recordInfo) throws SipException, InvalidArgumentException, ParseException;
 
     /**
      * 录像播放推送完成时发送MediaStatus消息
@@ -109,13 +112,13 @@ public interface ISIPCommanderForPlatform {
      * @param sendRtpItem
      * @return
      */
-    boolean sendMediaStatusNotify(ParentPlatform platform, SendRtpItem sendRtpItem);
+    void sendMediaStatusNotify(ParentPlatform platform, SendRtpItem sendRtpItem) throws SipException, InvalidArgumentException, ParseException;
 
     /**
      * 向发起点播的上级回复bye
      * @param platform 平台信息
      * @param callId  callId
      */
-    void streamByeCmd(ParentPlatform platform, String callId);
-    void streamByeCmd(ParentPlatform platform, SendRtpItem sendRtpItem);
+    void streamByeCmd(ParentPlatform platform, String callId) throws SipException, InvalidArgumentException, ParseException;
+    void streamByeCmd(ParentPlatform platform, SendRtpItem sendRtpItem) throws SipException, InvalidArgumentException, ParseException;
 }

+ 74 - 42
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java

@@ -10,6 +10,9 @@ import javax.sip.header.*;
 import javax.sip.message.Request;
 
 import com.genersoft.iot.vmp.common.StreamInfo;
+import com.genersoft.iot.vmp.gb28181.bean.SipMsgInfo;
+import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
+import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
 import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
@@ -17,6 +20,7 @@ import com.genersoft.iot.vmp.utils.GitUtil;
 import gov.nist.javax.sip.SipProviderImpl;
 import gov.nist.javax.sip.SipStackImpl;
 import gov.nist.javax.sip.message.SIPRequest;
+import gov.nist.javax.sip.message.SIPResponse;
 import gov.nist.javax.sip.stack.SIPDialog;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
@@ -168,34 +172,37 @@ public class SIPRequestHeaderProvider {
 		return request;
 	}
 
-	public Request createByteRequest(Device device, String channelId, String viaTag, String fromTag, String toTag, String callId) throws ParseException, InvalidArgumentException, PeerUnavailableException {
+	public Request createByteRequest(Device device, String channelId, SipTransactionInfo transactionInfo) throws ParseException, InvalidArgumentException, PeerUnavailableException {
 		Request request = null;
 		//请求行
 		SipURI requestLine = sipFactory.createAddressFactory().createSipURI(channelId, device.getHostAddress());
 		// via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(sipConfig.getIp(), sipConfig.getPort(), device.getTransport(), viaTag);
+		ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(sipConfig.getIp(), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag());
 		viaHeaders.add(viaHeader);
 		//from
 		SipURI fromSipURI = sipFactory.createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain());
 		Address fromAddress = sipFactory.createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = sipFactory.createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack
+		FromHeader fromHeader = sipFactory.createHeaderFactory().createFromHeader(fromAddress, transactionInfo.getFromTag());
 		//to
 		SipURI toSipURI = sipFactory.createAddressFactory().createSipURI(channelId,device.getHostAddress());
 		Address toAddress = sipFactory.createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = sipFactory.createHeaderFactory().createToHeader(toAddress,toTag);
+		ToHeader toHeader = sipFactory.createHeaderFactory().createToHeader(toAddress,	transactionInfo.getToTag());
 
 		//Forwards
 		MaxForwardsHeader maxForwards = sipFactory.createHeaderFactory().createMaxForwardsHeader(70);
 
 		//ceq
 		CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE);
-		CallIdHeader callIdHeader = sipFactory.createHeaderFactory().createCallIdHeader(callId);
+		CallIdHeader callIdHeader = sipFactory.createHeaderFactory().createCallIdHeader(transactionInfo.getCallId());
 		request = sipFactory.createMessageFactory().createRequest(requestLine, Request.BYE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
 
 		request.addHeader(SipUtils.createUserAgentHeader(sipFactory, gitUtil));
 
 		Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getIp()+":"+sipConfig.getPort()));
+		request.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
+
+		request.addHeader(SipUtils.createUserAgentHeader(sipFactory, gitUtil));
 
 		return request;
 	}
@@ -251,47 +258,72 @@ public class SIPRequestHeaderProvider {
 		return request;
 	}
 
-	public Request createInfoRequest(Device device, StreamInfo streamInfo, String content)
+	public SIPRequest createInfoRequest(Device device, String channelId, String content, SipTransactionInfo transactionInfo)
 			throws SipException, ParseException, InvalidArgumentException {
-		if (streamInfo == null) {
-			return null;
-		}
-		Request request = null;
-		SIPDialog dialog = streamSession.getDialogByStream(streamInfo.getDeviceID(), streamInfo.getChannelId(), streamInfo.getStream());
-		if (dialog == null) {
+		if (device == null || transactionInfo == null) {
 			return null;
 		}
+		SIPRequest request = null;
+		//请求行
+		SipURI requestLine = sipFactory.createAddressFactory().createSipURI(channelId, device.getHostAddress());
+		// via
+		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+		ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(sipConfig.getIp(), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag());
+		viaHeaders.add(viaHeader);
+		//from
+		SipURI fromSipURI = sipFactory.createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain());
+		Address fromAddress = sipFactory.createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = sipFactory.createHeaderFactory().createFromHeader(fromAddress, transactionInfo.getFromTag());
+		//to
+		SipURI toSipURI = sipFactory.createAddressFactory().createSipURI(channelId,device.getHostAddress());
+		Address toAddress = sipFactory.createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = sipFactory.createHeaderFactory().createToHeader(toAddress,	transactionInfo.getToTag());
+
+		//Forwards
+		MaxForwardsHeader maxForwards = sipFactory.createHeaderFactory().createMaxForwardsHeader(70);
+
+		//ceq
+		CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INFO);
+		CallIdHeader callIdHeader = sipFactory.createHeaderFactory().createCallIdHeader(transactionInfo.getCallId());
+		request = (SIPRequest)sipFactory.createMessageFactory().createRequest(requestLine, Request.INFO, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
 
-		SipStack sipStack = udpSipProvider.getSipStack();
-		SIPDialog sipDialog = ((SipStackImpl) sipStack).putDialog(dialog);
-		if (dialog != sipDialog) {
-			dialog = sipDialog;
-		}else {
-			dialog.setSipProvider(udpSipProvider);
+		request.addHeader(SipUtils.createUserAgentHeader(sipFactory, gitUtil));
+
+		Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getIp()+":"+sipConfig.getPort()));
+		request.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
+
+		request.addHeader(SipUtils.createUserAgentHeader(sipFactory, gitUtil));
+
+		if (content != null) {
+			ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application",
+					"MANSRTSP");
+			request.setContent(content, contentTypeHeader);
 		}
-		streamSession.put(streamInfo.getDeviceID(), streamInfo.getChannelId(), dialog.getCallId().getCallId(), dialog);
-		Request infoRequest = dialog.createRequest(Request.INFO);
-		SipURI sipURI = (SipURI) infoRequest.getRequestURI();
-		sipURI.setHost(device.getIp());
-		sipURI.setPort(device.getPort());
-		sipURI.setUser(streamInfo.getChannelId());
-
-		ViaHeader viaHeader = (ViaHeader) infoRequest.getHeader(ViaHeader.NAME);
-		viaHeader.setRPort();
-		// 增加Contact header
-		Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory()
-				.createSipURI(sipConfig.getId(), sipConfig.getIp() + ":" + sipConfig.getPort()));
-		infoRequest.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
-		infoRequest.addHeader(SipUtils.createUserAgentHeader(sipFactory, gitUtil));
-
-		ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application",
-				"MANSRTSP");
-		infoRequest.setContent(content, contentTypeHeader);
-
-		CSeqHeader cSeqHeader = (CSeqHeader)infoRequest.getHeader(CSeqHeader.NAME);
-		cSeqHeader.setSeqNumber(redisCatchStorage.getCSEQ());
-		// ceq
-		infoRequest.addHeader(cSeqHeader);
-		return infoRequest;
+		return request;
+	}
+
+	public Request createAckRequest(SipURI sipURI, SIPResponse sipResponse) throws ParseException, InvalidArgumentException, PeerUnavailableException {
+
+		// via
+		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+		ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(sipConfig.getIp(), sipConfig.getPort(), sipResponse.getTopmostViaHeader().getTransport(), SipUtils.getNewViaTag());
+		viaHeaders.add(viaHeader);
+
+		//Forwards
+		MaxForwardsHeader maxForwards = sipFactory.createHeaderFactory().createMaxForwardsHeader(70);
+
+		//ceq
+		CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(sipResponse.getCSeqHeader().getSeqNumber(), Request.ACK);
+
+		Request request = sipFactory.createMessageFactory().createRequest(sipURI, Request.ACK, sipResponse.getCallIdHeader(), cSeqHeader, sipResponse.getFromHeader(), sipResponse.getToHeader(), viaHeaders, maxForwards);
+
+		request.addHeader(SipUtils.createUserAgentHeader(sipFactory, gitUtil));
+
+		Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getIp()+":"+sipConfig.getPort()));
+		request.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
+
+		request.addHeader(SipUtils.createUserAgentHeader(sipFactory, gitUtil));
+
+		return request;
 	}
 }

+ 1432 - 1775
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java

@@ -5,6 +5,7 @@ import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.conf.DynamicTask;
 import com.genersoft.iot.vmp.conf.SipConfig;
 import com.genersoft.iot.vmp.conf.UserSetting;
+import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
@@ -22,17 +23,20 @@ import com.genersoft.iot.vmp.service.bean.SSRCInfo;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.utils.GitUtil;
+import gov.nist.javax.sip.SIPConstants;
 import gov.nist.javax.sip.SipProviderImpl;
 import gov.nist.javax.sip.SipStackImpl;
-import gov.nist.javax.sip.message.MessageFactoryImpl;
 import gov.nist.javax.sip.message.SIPRequest;
+import gov.nist.javax.sip.message.SIPResponse;
 import gov.nist.javax.sip.stack.SIPClientTransaction;
+import gov.nist.javax.sip.stack.SIPClientTransactionImpl;
 import gov.nist.javax.sip.stack.SIPDialog;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.context.annotation.DependsOn;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
 
@@ -41,1791 +45,1444 @@ import javax.sip.address.Address;
 import javax.sip.address.SipURI;
 import javax.sip.header.*;
 import javax.sip.message.Request;
+import javax.sip.message.Response;
 import java.lang.reflect.Field;
 import java.text.ParseException;
 import java.util.HashSet;
 
-/**    
- * @description:设备能力接口,用于定义设备的控制、查询能力   
+/**
+ * @description:设备能力接口,用于定义设备的控制、查询能力
  * @author: swwheihei
- * @date:   2020年5月3日 下午9:22:48     
+ * @date: 2020年5月3日 下午9:22:48
  */
 @Component
 @DependsOn("sipLayer")
 public class SIPCommander implements ISIPCommander {
 
-	private final Logger logger = LoggerFactory.getLogger(SIPCommander.class);
-
-	@Autowired
-	private SipConfig sipConfig;
-
-	@Autowired
-	private SipFactory sipFactory;
-
-	@Autowired
-	private GitUtil gitUtil;
-
-	@Autowired
-	@Qualifier(value="tcpSipProvider")
-	private SipProviderImpl tcpSipProvider;
-
-	@Autowired
-	@Qualifier(value="udpSipProvider")
-	private SipProviderImpl udpSipProvider;
-
-	@Autowired
-	private SIPRequestHeaderProvider headerProvider;
-	
-	@Autowired
-	private VideoStreamSessionManager streamSession;
-
-	@Autowired
-	private IVideoManagerStorage storager;
-
-	@Autowired
-	private IRedisCatchStorage redisCatchStorage;
-
-	@Autowired
-	private UserSetting userSetting;
-
-	@Autowired
-	private ZlmHttpHookSubscribe subscribe;
-
-	@Autowired
-	private SipSubscribe sipSubscribe;
-
-	@Autowired
-	private IMediaServerService mediaServerService;
-
-	@Autowired
-	private DynamicTask dynamicTask;
-
-
-	/**
-	 * 云台方向放控制,使用配置文件中的默认镜头移动速度
-	 * 
-	 * @param device  控制设备
-	 * @param channelId  预览通道
-	 * @param leftRight  镜头左移右移 0:停止 1:左移 2:右移
-     * @param upDown     镜头上移下移 0:停止 1:上移 2:下移
-	 */
-	@Override
-	public boolean ptzdirectCmd(Device device, String channelId, int leftRight, int upDown) {
-		return ptzCmd(device, channelId, leftRight, upDown, 0, sipConfig.getPtzSpeed(), 0);
-	}
-
-	/**
-	 * 云台方向放控制
-	 * 
-	 * @param device  控制设备
-	 * @param channelId  预览通道
-	 * @param leftRight  镜头左移右移 0:停止 1:左移 2:右移
-     * @param upDown     镜头上移下移 0:停止 1:上移 2:下移
-     * @param moveSpeed  镜头移动速度
-	 */
-	@Override
-	public boolean ptzdirectCmd(Device device, String channelId, int leftRight, int upDown, int moveSpeed) {
-		return ptzCmd(device, channelId, leftRight, upDown, 0, moveSpeed, 0);
-	}
-
-	/**
-	 * 云台缩放控制,使用配置文件中的默认镜头缩放速度
-	 * 
-	 * @param device  控制设备
-	 * @param channelId  预览通道
-     * @param inOut      镜头放大缩小 0:停止 1:缩小 2:放大
-	 */  
-	@Override
-	public boolean ptzZoomCmd(Device device, String channelId, int inOut) {
-		return ptzCmd(device, channelId, 0, 0, inOut, 0, sipConfig.getPtzSpeed());
-	}
-
-	/**
-	 * 云台缩放控制
-	 * 
-	 * @param device  控制设备
-	 * @param channelId  预览通道
-     * @param inOut      镜头放大缩小 0:停止 1:缩小 2:放大
-     * @param zoomSpeed  镜头缩放速度
-	 */ 
-	@Override
-	public boolean ptzZoomCmd(Device device, String channelId, int inOut, int zoomSpeed) {
-		return ptzCmd(device, channelId, 0, 0, inOut, 0, zoomSpeed);
-	}
-  
-   /**
-	* 云台指令码计算 
-	*
-    * @param leftRight  镜头左移右移 0:停止 1:左移 2:右移
-    * @param upDown     镜头上移下移 0:停止 1:上移 2:下移
-    * @param inOut      镜头放大缩小 0:停止 1:缩小 2:放大
-    * @param moveSpeed  镜头移动速度 默认 0XFF (0-255)
-    * @param zoomSpeed  镜头缩放速度 默认 0X1 (0-255)
-    */
-    public static String cmdString(int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed) {
-		int cmdCode = 0;
-		if (leftRight == 2) {
-			cmdCode|=0x01;		// 右移
-		} else if(leftRight == 1) {
-			cmdCode|=0x02;		// 左移
-		}
-		if (upDown == 2) {
-			cmdCode|=0x04;		// 下移
-		} else if(upDown == 1) {
-			cmdCode|=0x08;		// 上移
-		}
-		if (inOut == 2) {
-			cmdCode |= 0x10;	// 放大
-		} else if(inOut == 1) {
-			cmdCode |= 0x20;	// 缩小
-		}
-		StringBuilder builder = new StringBuilder("A50F01");
-		String strTmp;
-		strTmp = String.format("%02X", cmdCode);
-		builder.append(strTmp, 0, 2);
-		strTmp = String.format("%02X", moveSpeed);
-		builder.append(strTmp, 0, 2);
-		builder.append(strTmp, 0, 2);
-		strTmp = String.format("%X", zoomSpeed);
-		builder.append(strTmp, 0, 1).append("0");
-		//计算校验码
-		int checkCode = (0XA5 + 0X0F + 0X01 + cmdCode + moveSpeed + moveSpeed + (zoomSpeed /*<< 4*/ & 0XF0)) % 0X100;
-		strTmp = String.format("%02X", checkCode);
-		builder.append(strTmp, 0, 2);
-		return builder.toString();
-}
-
-   /**
-	* 云台指令码计算 
-	*
-	 * @param cmdCode 		指令码
-	 * @param parameter1	数据1
-	 * @param parameter2	数据2
-	 * @param combineCode2	组合码2
-	 */
+    private final Logger logger = LoggerFactory.getLogger(SIPCommander.class);
+
+    @Autowired
+    private SipConfig sipConfig;
+
+    @Autowired
+    private SipFactory sipFactory;
+
+    @Autowired
+    private GitUtil gitUtil;
+
+    @Autowired
+    @Qualifier(value = "tcpSipProvider")
+    private SipProviderImpl tcpSipProvider;
+
+    @Autowired
+    @Qualifier(value = "udpSipProvider")
+    private SipProviderImpl udpSipProvider;
+
+    @Autowired
+    private SIPRequestHeaderProvider headerProvider;
+
+    @Autowired
+    private VideoStreamSessionManager streamSession;
+
+    @Autowired
+    private UserSetting userSetting;
+
+    @Autowired
+    private ZlmHttpHookSubscribe subscribe;
+
+    @Autowired
+    private SipSubscribe sipSubscribe;
+
+    @Autowired
+    private IMediaServerService mediaServerService;
+
+
+    /**
+     * 云台方向放控制,使用配置文件中的默认镜头移动速度
+     *
+     * @param device    控制设备
+     * @param channelId 预览通道
+     * @param leftRight 镜头左移右移 0:停止 1:左移 2:右移
+     * @param upDown    镜头上移下移 0:停止 1:上移 2:下移
+     */
+    @Override
+    public void ptzdirectCmd(Device device, String channelId, int leftRight, int upDown) throws InvalidArgumentException, ParseException, SipException {
+        ptzCmd(device, channelId, leftRight, upDown, 0, sipConfig.getPtzSpeed(), 0);
+    }
+
+    /**
+     * 云台方向放控制
+     *
+     * @param device    控制设备
+     * @param channelId 预览通道
+     * @param leftRight 镜头左移右移 0:停止 1:左移 2:右移
+     * @param upDown    镜头上移下移 0:停止 1:上移 2:下移
+     * @param moveSpeed 镜头移动速度
+     */
+    @Override
+    public void ptzdirectCmd(Device device, String channelId, int leftRight, int upDown, int moveSpeed) throws InvalidArgumentException, ParseException, SipException {
+        ptzCmd(device, channelId, leftRight, upDown, 0, moveSpeed, 0);
+    }
+
+    /**
+     * 云台缩放控制,使用配置文件中的默认镜头缩放速度
+     *
+     * @param device    控制设备
+     * @param channelId 预览通道
+     * @param inOut     镜头放大缩小 0:停止 1:缩小 2:放大
+     */
+    @Override
+    public void ptzZoomCmd(Device device, String channelId, int inOut) throws InvalidArgumentException, ParseException, SipException {
+        ptzCmd(device, channelId, 0, 0, inOut, 0, sipConfig.getPtzSpeed());
+    }
+
+    /**
+     * 云台缩放控制
+     *
+     * @param device    控制设备
+     * @param channelId 预览通道
+     * @param inOut     镜头放大缩小 0:停止 1:缩小 2:放大
+     * @param zoomSpeed 镜头缩放速度
+     */
+    @Override
+    public void ptzZoomCmd(Device device, String channelId, int inOut, int zoomSpeed) throws InvalidArgumentException, ParseException, SipException {
+        ptzCmd(device, channelId, 0, 0, inOut, 0, zoomSpeed);
+    }
+
+    /**
+     * 云台指令码计算
+     *
+     * @param cmdCode      指令码
+     * @param parameter1   数据1
+     * @param parameter2   数据2
+     * @param combineCode2 组合码2
+     */
     public static String frontEndCmdString(int cmdCode, int parameter1, int parameter2, int combineCode2) {
-		StringBuilder builder = new StringBuilder("A50F01");
-		String strTmp;
-		strTmp = String.format("%02X", cmdCode);
-		builder.append(strTmp, 0, 2);
-		strTmp = String.format("%02X", parameter1);
-		builder.append(strTmp, 0, 2);
-		strTmp = String.format("%02X", parameter2);
-		builder.append(strTmp, 0, 2);
-		strTmp = String.format("%X", combineCode2);
-		builder.append(strTmp, 0, 1).append("0");
-		//计算校验码
-		int checkCode = (0XA5 + 0X0F + 0X01 + cmdCode + parameter1 + parameter2 + (combineCode2 & 0XF0)) % 0X100;
-		strTmp = String.format("%02X", checkCode);
-		builder.append(strTmp, 0, 2);
-		return builder.toString();
-	}
-
-	/**
-	 * 云台控制,支持方向与缩放控制
-	 * 
-	 * @param device  	控制设备
-	 * @param channelId	预览通道
-	 * @param leftRight	镜头左移右移 0:停止 1:左移 2:右移
-     * @param upDown	镜头上移下移 0:停止 1:上移 2:下移
-     * @param inOut		镜头放大缩小 0:停止 1:缩小 2:放大
-     * @param moveSpeed	镜头移动速度
-     * @param zoomSpeed	镜头缩放速度
-	 */
-	@Override
-	public boolean ptzCmd(Device device, String channelId, int leftRight, int upDown, int inOut, int moveSpeed,
-			int zoomSpeed) {
-		try {
-			String cmdStr= cmdString(leftRight, upDown, inOut, moveSpeed, zoomSpeed);
-			StringBuffer ptzXml = new StringBuffer(200);
-			String charset = device.getCharset();
-			ptzXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-			ptzXml.append("<Control>\r\n");
-			ptzXml.append("<CmdType>DeviceControl</CmdType>\r\n");
-			ptzXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-			ptzXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
-			ptzXml.append("<PTZCmd>" + cmdStr + "</PTZCmd>\r\n");
-			ptzXml.append("<Info>\r\n");
-			ptzXml.append("<ControlPriority>5</ControlPriority>\r\n");
-			ptzXml.append("</Info>\r\n");
-			ptzXml.append("</Control>\r\n");
-
-			CallIdHeader callIdHeader = device.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
-					: udpSipProvider.getNewCallId();
-
-			Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
-			
-			transmitRequest(device, request);
-			return true;
-		} catch (SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-		} 
-		return false;
-	}
-
-	/**
-	 * 前端控制,包括PTZ指令、FI指令、预置位指令、巡航指令、扫描指令和辅助开关指令
-	 * 
-	 * @param device  		控制设备
-	 * @param channelId		预览通道
-	 * @param cmdCode		指令码
-     * @param parameter1	数据1
-     * @param parameter2	数据2
-     * @param combineCode2	组合码2
-	 */
-	@Override
-	public boolean frontEndCmd(Device device, String channelId, int cmdCode, int parameter1, int parameter2, int combineCode2) {
-		try {
-			String cmdStr= frontEndCmdString(cmdCode, parameter1, parameter2, combineCode2);
-			logger.debug("控制字符串:" + cmdStr);
-			StringBuffer ptzXml = new StringBuffer(200);
-			String charset = device.getCharset();
-			ptzXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-			ptzXml.append("<Control>\r\n");
-			ptzXml.append("<CmdType>DeviceControl</CmdType>\r\n");
-			ptzXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-			ptzXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
-			ptzXml.append("<PTZCmd>" + cmdStr + "</PTZCmd>\r\n");
-			ptzXml.append("<Info>\r\n");
-			ptzXml.append("<ControlPriority>5</ControlPriority>\r\n");
-			ptzXml.append("</Info>\r\n");
-			ptzXml.append("</Control>\r\n");
-			
-
-			CallIdHeader callIdHeader = device.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
-					: udpSipProvider.getNewCallId();
-
-			Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
-			transmitRequest(device, request);
-			return true;
-		} catch (SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-		} 
-		return false;
-	}
-
-	/**
-	 * 前端控制指令(用于转发上级指令)
-	 * @param device		控制设备
-	 * @param channelId		预览通道
-	 * @param cmdString		前端控制指令串
-	 */
-	@Override
-	public boolean fronEndCmd(Device device, String channelId, String cmdString, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) {
-		try {
-			StringBuffer ptzXml = new StringBuffer(200);
-			String charset = device.getCharset();
-			ptzXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-			ptzXml.append("<Control>\r\n");
-			ptzXml.append("<CmdType>DeviceControl</CmdType>\r\n");
-			ptzXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-			ptzXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
-			ptzXml.append("<PTZCmd>" + cmdString + "</PTZCmd>\r\n");
-			ptzXml.append("<Info>\r\n");
-			ptzXml.append("<ControlPriority>5</ControlPriority>\r\n");
-			ptzXml.append("</Info>\r\n");
-			ptzXml.append("</Control>\r\n");
-			
-
-			CallIdHeader callIdHeader = device.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
-					: udpSipProvider.getNewCallId();
-
-			Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
-			transmitRequest(device, request, errorEvent, okEvent);
-			return true;
-		} catch (SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-		} 
-		return false;
-	}
-	
-	 /**
-	 * 	请求预览视频流
-	  * @param device  视频设备
-	  * @param channelId  预览通道
-	  * @param event hook订阅
-	  * @param errorEvent sip错误订阅
-	*/
-	@Override
-	public void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
-							  ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) {
-		String stream = ssrcInfo.getStream();
-		try {
-			if (device == null) {
-				return;
-			}
-//			String streamMode = device.getStreamMode().toUpperCase();
-
-			logger.info("{} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
-			HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId());
-			subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json)->{
-				if (event != null) {
-					event.response(mediaServerItemInUse, json);
-					subscribe.removeSubscribe(hookSubscribe);
-				}
-			});
-			//
-			StringBuffer content = new StringBuffer(200);
-			content.append("v=0\r\n");
-			content.append("o="+ channelId+" 0 0 IN IP4 "+ mediaServerItem.getSdpIp() +"\r\n");
-			content.append("s=Play\r\n");
-			content.append("c=IN IP4 "+ mediaServerItem.getSdpIp() +"\r\n");
-			content.append("t=0 0\r\n");
-
-			if (userSetting.isSeniorSdp()) {
-				if("TCP-PASSIVE".equalsIgnoreCase(device.getStreamMode())) {
-					content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
-				}else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) {
-					content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
-				}else if("UDP".equalsIgnoreCase(device.getStreamMode())) {
-					content.append("m=video "+ ssrcInfo.getPort() +" RTP/AVP 96 126 125 99 34 98 97\r\n");
-				}
-				content.append("a=recvonly\r\n");
-				content.append("a=rtpmap:96 PS/90000\r\n");
-				content.append("a=fmtp:126 profile-level-id=42e01e\r\n");
-				content.append("a=rtpmap:126 H264/90000\r\n");
-				content.append("a=rtpmap:125 H264S/90000\r\n");
-				content.append("a=fmtp:125 profile-level-id=42e01e\r\n");
-				content.append("a=rtpmap:99 H265/90000\r\n");
-				content.append("a=rtpmap:98 H264/90000\r\n");
-				content.append("a=rtpmap:97 MPEG4/90000\r\n");
-				if("TCP-PASSIVE".equalsIgnoreCase(device.getStreamMode())){ // tcp被动模式
-					content.append("a=setup:passive\r\n");
-					content.append("a=connection:new\r\n");
-				}else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) { // tcp主动模式
-					content.append("a=setup:active\r\n");
-					content.append("a=connection:new\r\n");
-				}
-			}else {
-				if("TCP-PASSIVE".equalsIgnoreCase(device.getStreamMode())) {
-					content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 97 98 99\r\n");
-				}else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) {
-					content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 97 98 99\r\n");
-				}else if("UDP".equalsIgnoreCase(device.getStreamMode())) {
-					content.append("m=video "+ ssrcInfo.getPort() +" RTP/AVP 96 97 98 99\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");
-				content.append("a=rtpmap:99 H265/90000\r\n");
-				if ("TCP-PASSIVE".equalsIgnoreCase(device.getStreamMode())) { // tcp被动模式
-					content.append("a=setup:passive\r\n");
-					content.append("a=connection:new\r\n");
-				} else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) { // tcp主动模式
-					content.append("a=setup:active\r\n");
-					content.append("a=connection:new\r\n");
-				}
-			}
-
-			content.append("y="+ssrcInfo.getSsrc()+"\r\n");//ssrc
-			// f字段:f= v/编码格式/分辨率/帧率/码率类型/码率大小a/编码格式/码率大小/采样率
+        StringBuilder builder = new StringBuilder("A50F01");
+        String strTmp;
+        strTmp = String.format("%02X", cmdCode);
+        builder.append(strTmp, 0, 2);
+        strTmp = String.format("%02X", parameter1);
+        builder.append(strTmp, 0, 2);
+        strTmp = String.format("%02X", parameter2);
+        builder.append(strTmp, 0, 2);
+        strTmp = String.format("%X", combineCode2);
+        builder.append(strTmp, 0, 1).append("0");
+        //计算校验码
+        int checkCode = (0XA5 + 0X0F + 0X01 + cmdCode + parameter1 + parameter2 + (combineCode2 & 0XF0)) % 0X100;
+        strTmp = String.format("%02X", checkCode);
+        builder.append(strTmp, 0, 2);
+        return builder.toString();
+    }
+
+    /**
+     * 云台控制,支持方向与缩放控制
+     *
+     * @param device    控制设备
+     * @param channelId 预览通道
+     * @param leftRight 镜头左移右移 0:停止 1:左移 2:右移
+     * @param upDown    镜头上移下移 0:停止 1:上移 2:下移
+     * @param inOut     镜头放大缩小 0:停止 1:缩小 2:放大
+     * @param moveSpeed 镜头移动速度
+     * @param zoomSpeed 镜头缩放速度
+     */
+    @Override
+    public void ptzCmd(Device device, String channelId, int leftRight, int upDown, int inOut, int moveSpeed,
+                       int zoomSpeed) throws InvalidArgumentException, SipException, ParseException {
+        String cmdStr = SipUtils.cmdString(leftRight, upDown, inOut, moveSpeed, zoomSpeed);
+        StringBuffer ptzXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        ptzXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        ptzXml.append("<Control>\r\n");
+        ptzXml.append("<CmdType>DeviceControl</CmdType>\r\n");
+        ptzXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        ptzXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
+        ptzXml.append("<PTZCmd>" + cmdStr + "</PTZCmd>\r\n");
+        ptzXml.append("<Info>\r\n");
+        ptzXml.append("<ControlPriority>5</ControlPriority>\r\n");
+        ptzXml.append("</Info>\r\n");
+        ptzXml.append("</Control>\r\n");
+
+        CallIdHeader callIdHeader = device.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+
+        Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
+
+        transmitRequest(device.getTransport(), request);
+    }
+
+    /**
+     * 前端控制,包括PTZ指令、FI指令、预置位指令、巡航指令、扫描指令和辅助开关指令
+     *
+     * @param device       控制设备
+     * @param channelId    预览通道
+     * @param cmdCode      指令码
+     * @param parameter1   数据1
+     * @param parameter2   数据2
+     * @param combineCode2 组合码2
+     */
+    @Override
+    public void frontEndCmd(Device device, String channelId, int cmdCode, int parameter1, int parameter2, int combineCode2) throws SipException, InvalidArgumentException, ParseException {
+
+        String cmdStr = frontEndCmdString(cmdCode, parameter1, parameter2, combineCode2);
+        StringBuffer ptzXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        ptzXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        ptzXml.append("<Control>\r\n");
+        ptzXml.append("<CmdType>DeviceControl</CmdType>\r\n");
+        ptzXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        ptzXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
+        ptzXml.append("<PTZCmd>" + cmdStr + "</PTZCmd>\r\n");
+        ptzXml.append("<Info>\r\n");
+        ptzXml.append("<ControlPriority>5</ControlPriority>\r\n");
+        ptzXml.append("</Info>\r\n");
+        ptzXml.append("</Control>\r\n");
+
+
+        CallIdHeader callIdHeader = device.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+
+        SIPRequest request = (SIPRequest) headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
+        transmitRequest(device.getTransport(), request);
+
+    }
+
+    /**
+     * 前端控制指令(用于转发上级指令)
+     *
+     * @param device    控制设备
+     * @param channelId 预览通道
+     * @param cmdString 前端控制指令串
+     */
+    @Override
+    public void fronEndCmd(Device device, String channelId, String cmdString, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer ptzXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        ptzXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        ptzXml.append("<Control>\r\n");
+        ptzXml.append("<CmdType>DeviceControl</CmdType>\r\n");
+        ptzXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        ptzXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
+        ptzXml.append("<PTZCmd>" + cmdString + "</PTZCmd>\r\n");
+        ptzXml.append("<Info>\r\n");
+        ptzXml.append("<ControlPriority>5</ControlPriority>\r\n");
+        ptzXml.append("</Info>\r\n");
+        ptzXml.append("</Control>\r\n");
+
+
+        CallIdHeader callIdHeader = device.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+
+        Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
+        transmitRequest(device.getTransport(), request, errorEvent, okEvent);
+
+    }
+
+    /**
+     * 请求预览视频流
+     *
+     * @param device     视频设备
+     * @param channelId  预览通道
+     * @param event      hook订阅
+     * @param errorEvent sip错误订阅
+     */
+    @Override
+    public void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
+                              ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+        String stream = ssrcInfo.getStream();
+
+        if (device == null) {
+            return;
+        }
+
+        logger.info("{} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
+        HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId());
+        subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json) -> {
+            if (event != null) {
+                event.response(mediaServerItemInUse, json);
+                subscribe.removeSubscribe(hookSubscribe);
+            }
+        });
+        //
+        StringBuffer content = new StringBuffer(200);
+        content.append("v=0\r\n");
+        content.append("o=" + channelId + " 0 0 IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
+        content.append("s=Play\r\n");
+        content.append("c=IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
+        content.append("t=0 0\r\n");
+
+        if (userSetting.isSeniorSdp()) {
+            if ("TCP-PASSIVE".equalsIgnoreCase(device.getStreamMode())) {
+                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
+            } else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) {
+                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
+            } else if ("UDP".equalsIgnoreCase(device.getStreamMode())) {
+                content.append("m=video " + ssrcInfo.getPort() + " RTP/AVP 96 126 125 99 34 98 97\r\n");
+            }
+            content.append("a=recvonly\r\n");
+            content.append("a=rtpmap:96 PS/90000\r\n");
+            content.append("a=fmtp:126 profile-level-id=42e01e\r\n");
+            content.append("a=rtpmap:126 H264/90000\r\n");
+            content.append("a=rtpmap:125 H264S/90000\r\n");
+            content.append("a=fmtp:125 profile-level-id=42e01e\r\n");
+            content.append("a=rtpmap:99 H265/90000\r\n");
+            content.append("a=rtpmap:98 H264/90000\r\n");
+            content.append("a=rtpmap:97 MPEG4/90000\r\n");
+            if ("TCP-PASSIVE".equalsIgnoreCase(device.getStreamMode())) { // tcp被动模式
+                content.append("a=setup:passive\r\n");
+                content.append("a=connection:new\r\n");
+            } else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) { // tcp主动模式
+                content.append("a=setup:active\r\n");
+                content.append("a=connection:new\r\n");
+            }
+        } else {
+            if ("TCP-PASSIVE".equalsIgnoreCase(device.getStreamMode())) {
+                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 97 98 99\r\n");
+            } else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) {
+                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 97 98 99\r\n");
+            } else if ("UDP".equalsIgnoreCase(device.getStreamMode())) {
+                content.append("m=video " + ssrcInfo.getPort() + " RTP/AVP 96 97 98 99\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");
+            content.append("a=rtpmap:99 H265/90000\r\n");
+            if ("TCP-PASSIVE".equalsIgnoreCase(device.getStreamMode())) { // tcp被动模式
+                content.append("a=setup:passive\r\n");
+                content.append("a=connection:new\r\n");
+            } else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) { // tcp主动模式
+                content.append("a=setup:active\r\n");
+                content.append("a=connection:new\r\n");
+            }
+        }
+
+        content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc
+        // f字段:f= v/编码格式/分辨率/帧率/码率类型/码率大小a/编码格式/码率大小/采样率
 //			content.append("f=v/2/5/25/1/4000a/1/8/1" + "\r\n"); // 未发现支持此特性的设备
 
-			CallIdHeader callIdHeader = device.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
-					: udpSipProvider.getNewCallId();
-
-			Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), null, SipUtils.getNewFromTag(), null, ssrcInfo.getSsrc(), callIdHeader);
-
-			transmitRequest(device, request, (e -> {
-				streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
-				mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
-				errorEvent.response(e);
-			}), e ->{
-				// 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值
-				streamSession.put(device.getDeviceId(), channelId ,"play", stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), ((ResponseEvent)e.event).getClientTransaction(), VideoStreamSessionManager.SessionType.play);
-				Dialog sipDialog = null;
-				if (e.dialog == null) {
-					SIPClientTransaction clientTransaction = (SIPClientTransaction)((ResponseEvent)e.event).getClientTransaction();
-					sipDialog = new SIPDialog(clientTransaction, clientTransaction.getLastResponse());
-				}else {
-					sipDialog = e.dialog;
-				}
-				streamSession.put(device.getDeviceId(), channelId ,"play", sipDialog);
-				okEvent.response(e);
-			});
-
-			
-		} catch ( SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-		}
-	}
-	
-	/**
-	 * 请求回放视频流
-	 * 
-	 * @param device  视频设备
-	 * @param channelId  预览通道
-	 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
-	 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
-	 */ 
-	@Override
-	public void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
-								  String startTime, String endTime, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
-								  SipSubscribe.Event okEvent,SipSubscribe.Event errorEvent) {
-		try {
-
-			logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
-
-			StringBuffer content = new StringBuffer(200);
-	        content.append("v=0\r\n");
-	        content.append("o="+channelId+" 0 0 IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
-	        content.append("s=Playback\r\n");
-	        content.append("u="+channelId+":0\r\n");
-	        content.append("c=IN IP4 "+mediaServerItem.getSdpIp()+"\r\n");
-	        content.append("t="+DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime)+" "
-					+DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime) +"\r\n");
-
-			String streamMode = device.getStreamMode();
-
-			if (userSetting.isSeniorSdp()) {
-				if("TCP-PASSIVE".equalsIgnoreCase(streamMode)) {
-					content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
-				}else if ("TCP-ACTIVE".equalsIgnoreCase(streamMode)) {
-					content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
-				}else if("UDP".equalsIgnoreCase(streamMode)) {
-					content.append("m=video "+ ssrcInfo.getPort() +" RTP/AVP 96 126 125 99 34 98 97\r\n");
-				}
-				content.append("a=recvonly\r\n");
-				content.append("a=rtpmap:96 PS/90000\r\n");
-				content.append("a=fmtp:126 profile-level-id=42e01e\r\n");
-				content.append("a=rtpmap:126 H264/90000\r\n");
-				content.append("a=rtpmap:125 H264S/90000\r\n");
-				content.append("a=fmtp:125 profile-level-id=42e01e\r\n");
-				content.append("a=rtpmap:99 H265/90000\r\n");
-				content.append("a=rtpmap:98 H264/90000\r\n");
-				content.append("a=rtpmap:97 MPEG4/90000\r\n");
-				if("TCP-PASSIVE".equalsIgnoreCase(streamMode)){ // tcp被动模式
-					content.append("a=setup:passive\r\n");
-					content.append("a=connection:new\r\n");
-				}else if ("TCP-ACTIVE".equalsIgnoreCase(streamMode)) { // tcp主动模式
-					content.append("a=setup:active\r\n");
-					content.append("a=connection:new\r\n");
-				}
-			}else {
-				if("TCP-PASSIVE".equalsIgnoreCase(streamMode)) {
-					content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 97 98 99\r\n");
-				}else if ("TCP-ACTIVE".equalsIgnoreCase(streamMode)) {
-					content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 97 98 99\r\n");
-				}else if("UDP".equalsIgnoreCase(streamMode)) {
-					content.append("m=video "+ ssrcInfo.getPort() +" RTP/AVP 96 97 98 99\r\n");
-				}
-				content.append("a=recvonly\r\n");
-				content.append("a=rtpmap:96 PS/90000\r\n");
-				content.append("a=rtpmap:97 MPEG4/90000\r\n");
-				content.append("a=rtpmap:98 H264/90000\r\n");
-				content.append("a=rtpmap:99 H265/90000\r\n");
-				if("TCP-PASSIVE".equalsIgnoreCase(streamMode)){ // tcp被动模式
-					content.append("a=setup:passive\r\n");
-					content.append("a=connection:new\r\n");
-				}else if ("TCP-ACTIVE".equalsIgnoreCase(streamMode)) { // tcp主动模式
-					content.append("a=setup:active\r\n");
-					content.append("a=connection:new\r\n");
-				}
-			}
-
-	        content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc
-
-			CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-					: udpSipProvider.getNewCallId();
-			HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
-			// 添加订阅
-			subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json)->{
-						if (hookEvent != null) {
-							InviteStreamInfo inviteStreamInfo = new InviteStreamInfo(mediaServerItemInUse, json, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream());
-							hookEvent.call(inviteStreamInfo);
-						}
-						subscribe.removeSubscribe(hookSubscribe);
-					});
-	        Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader, ssrcInfo.getSsrc());
-
-	        transmitRequest(device, request, errorEvent, event -> {
-				ResponseEvent responseEvent = (ResponseEvent) event.event;
-	        	streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), responseEvent.getClientTransaction(), VideoStreamSessionManager.SessionType.playback);
-				streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), event.dialog);
-				okEvent.response(event);
-			});
-			if (inviteStreamCallback != null) {
-				inviteStreamCallback.call(new InviteStreamInfo(mediaServerItem, null, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream()));
-			}
-		} catch ( SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-		}
-	}
-
-	/**
-	 * 请求历史媒体下载
-	 * 
-	 * @param device  视频设备
-	 * @param channelId  预览通道
-	 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
-	 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
-	 * @param downloadSpeed 下载倍速参数
-	 */ 
-	@Override
-	public void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
-								  String startTime, String endTime, int downloadSpeed, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
-								  SipSubscribe.Event errorEvent) {
-		try {
-			logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
-
-			StringBuffer content = new StringBuffer(200);
-	        content.append("v=0\r\n");
-	        content.append("o="+channelId+" 0 0 IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
-	        content.append("s=Download\r\n");
-	        content.append("u="+channelId+":0\r\n");
-	        content.append("c=IN IP4 "+mediaServerItem.getSdpIp()+"\r\n");
-	        content.append("t="+DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime)+" "
-					+DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime) +"\r\n");
-
-			String streamMode = device.getStreamMode().toUpperCase();
-
-			if (userSetting.isSeniorSdp()) {
-				if("TCP-PASSIVE".equals(streamMode)) {
-					content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
-				}else if ("TCP-ACTIVE".equals(streamMode)) {
-					content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
-				}else if("UDP".equals(streamMode)) {
-					content.append("m=video "+ ssrcInfo.getPort() +" RTP/AVP 96 126 125 99 34 98 97\r\n");
-				}
-				content.append("a=recvonly\r\n");
-				content.append("a=rtpmap:96 PS/90000\r\n");
-				content.append("a=fmtp:126 profile-level-id=42e01e\r\n");
-				content.append("a=rtpmap:126 H264/90000\r\n");
-				content.append("a=rtpmap:125 H264S/90000\r\n");
-				content.append("a=fmtp:125 profile-level-id=42e01e\r\n");
-				content.append("a=rtpmap:99 MP4V-ES/90000\r\n");
-				content.append("a=fmtp:99 profile-level-id=3\r\n");
-				content.append("a=rtpmap:98 H264/90000\r\n");
-				content.append("a=rtpmap:97 MPEG4/90000\r\n");
-				if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式
-					content.append("a=setup:passive\r\n");
-					content.append("a=connection:new\r\n");
-				}else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式
-					content.append("a=setup:active\r\n");
-					content.append("a=connection:new\r\n");
-				}
-			}else {
-				if("TCP-PASSIVE".equals(streamMode)) {
-					content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 97 98 99\r\n");
-				}else if ("TCP-ACTIVE".equals(streamMode)) {
-					content.append("m=video "+ ssrcInfo.getPort() +" TCP/RTP/AVP 96 97 98 99\r\n");
-				}else if("UDP".equals(streamMode)) {
-					content.append("m=video "+ ssrcInfo.getPort() +" RTP/AVP 96 97 98 99\r\n");
-				}
-				content.append("a=recvonly\r\n");
-				content.append("a=rtpmap:96 PS/90000\r\n");
-				content.append("a=rtpmap:97 MPEG4/90000\r\n");
-				content.append("a=rtpmap:98 H264/90000\r\n");
-				content.append("a=rtpmap:99 H265/90000\r\n");
-				if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式
-					content.append("a=setup:passive\r\n");
-					content.append("a=connection:new\r\n");
-				}else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式
-					content.append("a=setup:active\r\n");
-					content.append("a=connection:new\r\n");
-				}
-			}
-			content.append("a=downloadspeed:" + downloadSpeed + "\r\n");
-
-	        content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc
-
-			CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-					: udpSipProvider.getNewCallId();
-
-			HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, null, mediaServerItem.getId());
-			// 添加订阅
-			subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json)->{
-						hookEvent.call(new InviteStreamInfo(mediaServerItem, json, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream()));
-						subscribe.removeSubscribe(hookSubscribe);
-						hookSubscribe.getContent().put("regist", false);
-						hookSubscribe.getContent().put("schema", "rtsp");
-						// 添加流注销的订阅,注销了后向设备发送bye
-						subscribe.addSubscribe(hookSubscribe,
-								(MediaServerItem mediaServerItemForEnd, JSONObject jsonForEnd)->{
-									ClientTransaction transaction = streamSession.getTransaction(device.getDeviceId(), channelId, ssrcInfo.getStream(), callIdHeader.getCallId());
-									if (transaction != null) {
-										logger.info("[录像]下载结束, 发送BYE");
-										streamByeCmd(device.getDeviceId(), channelId, ssrcInfo.getStream(), callIdHeader.getCallId());
-									}
-								});
-					});
-
-	        Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader, ssrcInfo.getSsrc());
-			if (inviteStreamCallback != null) {
-				inviteStreamCallback.call(new InviteStreamInfo(mediaServerItem, null, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream()));
-			}
-	        transmitRequest(device, request, errorEvent, okEvent->{
-				ResponseEvent responseEvent = (ResponseEvent) okEvent.event;
-				streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), responseEvent.getClientTransaction(), VideoStreamSessionManager.SessionType.download);
-				streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), okEvent.dialog);
-			});
-
-
-		} catch ( SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-		}
-	}
-
-	/**
-	 * 视频流停止, 不使用回调
-	 */
-	@Override
-	public void streamByeCmd(String deviceId, String channelId, String stream, String callId) {
-		streamByeCmd(deviceId, channelId, stream, callId, null);
-	}
-
-	/**
-	 * 视频流停止
-	 */
-	@Override
-	public void streamByeCmd(String deviceId, String channelId, String stream, String callId, SipSubscribe.Event okEvent) {
-		try {
-			SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(deviceId, channelId, callId, stream);
-			ClientTransaction transaction = streamSession.getTransaction(deviceId, channelId, stream, callId);
-
-			if (transaction == null ) {
-				logger.warn("[ {} -> {}]停止视频流的时候发现事务已丢失", deviceId, channelId);
-				SipSubscribe.EventResult<Object> eventResult = new SipSubscribe.EventResult<>();
-				if (okEvent != null) {
-					okEvent.response(eventResult);
-				}
-				return;
-			}
-			SIPDialog dialog;
-			if (callId != null) {
-				dialog = streamSession.getDialogByCallId(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), callId);
-			}else {
-				if (stream == null && ssrcTransaction == null && ssrcTransaction.getStream() == null) {
-					return;
-				}
-				dialog = streamSession.getDialogByStream(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
-			}
-			mediaServerService.releaseSsrc(ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc());
-			mediaServerService.closeRTPServer(ssrcTransaction.getMediaServerId(), ssrcTransaction.getStream());
-			streamSession.remove(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
-
-			if (dialog == null) {
-				logger.warn("[ {} -> {}]停止视频流的时候发现对话已丢失", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
-				return;
-			}
-			SipStack sipStack = udpSipProvider.getSipStack();
-			SIPDialog sipDialog = ((SipStackImpl) sipStack).putDialog(dialog);
-			if (dialog != sipDialog) {
-				dialog = sipDialog;
-			}else {
-				dialog.setSipProvider(udpSipProvider);
-				try {
-					Field sipStackField = SIPDialog.class.getDeclaredField("sipStack");
-					sipStackField.setAccessible(true);
-					sipStackField.set(dialog, sipStack);
-					Field eventListenersField = SIPDialog.class.getDeclaredField("eventListeners");
-					eventListenersField.setAccessible(true);
-					eventListenersField.set(dialog, new HashSet<>());
-				} catch (NoSuchFieldException | IllegalAccessException e) {
-					e.printStackTrace();
-				}
-			}
-
-			Request byeRequest = dialog.createRequest(Request.BYE);
-			SipURI byeURI = (SipURI) byeRequest.getRequestURI();
-			SIPRequest request = (SIPRequest)transaction.getRequest();
-			byeURI.setHost(request.getRemoteAddress().getHostAddress());
-			byeURI.setPort(request.getRemotePort());
-			byeURI.setUser(channelId);
-			ViaHeader viaHeader = (ViaHeader) byeRequest.getHeader(ViaHeader.NAME);
-			String protocol = viaHeader.getTransport().toUpperCase();
-			viaHeader.setRPort();
-			// 增加Contact header
-			Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getIp()+":"+sipConfig.getPort()));
-			byeRequest.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
-			UserAgentHeader userAgentHeader = SipUtils.createUserAgentHeader(sipFactory, gitUtil);
-			byeRequest.addHeader(userAgentHeader);
-			ClientTransaction clientTransaction = null;
-			if("TCP".equals(protocol)) {
-				clientTransaction = tcpSipProvider.getNewClientTransaction(byeRequest);
-			} else if("UDP".equals(protocol)) {
-				clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest);
-			}
-
-			CallIdHeader callIdHeader = (CallIdHeader) byeRequest.getHeader(CallIdHeader.NAME);
-			if (okEvent != null) {
-				sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), okEvent);
-			}
-			CSeqHeader cSeqHeader = (CSeqHeader)byeRequest.getHeader(CSeqHeader.NAME);
-			cSeqHeader.setSeqNumber(redisCatchStorage.getCSEQ());
-			dialog.sendRequest(clientTransaction);
-
-		} catch (SipException | ParseException e) {
-			e.printStackTrace();
-		} catch (InvalidArgumentException e) {
-			throw new RuntimeException(e);
-		}
-	}
-
-	/**
-	 * 语音广播
-	 * 
-	 * @param device  视频设备
-	 * @param channelId  预览通道
-	 */
-	@Override
-	public boolean audioBroadcastCmd(Device device, String channelId) {
-		// 改为新的实现
-		return false;
-	}
-
-	/**
-	 * 语音广播
-	 * 
-	 * @param device  视频设备
-	 */
-	@Override
-	public boolean audioBroadcastCmd(Device device) {
-		try {
-			StringBuffer broadcastXml = new StringBuffer(200);
-			String charset = device.getCharset();
-			broadcastXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-			broadcastXml.append("<Notify>\r\n");
-			broadcastXml.append("<CmdType>Broadcast</CmdType>\r\n");
-			broadcastXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-			broadcastXml.append("<SourceID>" + sipConfig.getId() + "</SourceID>\r\n");
-			broadcastXml.append("<TargetID>" + device.getDeviceId() + "</TargetID>\r\n");
-			broadcastXml.append("</Notify>\r\n");
-
-			CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-					: udpSipProvider.getNewCallId();
-								
-			Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
-			transmitRequest(device, request);
-			return true;
-		} catch (SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-		} 
-		return false;
-	}
-	@Override
-	public void audioBroadcastCmd(Device device, SipSubscribe.Event errorEvent) {
-		try {
-			StringBuffer broadcastXml = new StringBuffer(200);
-			String charset = device.getCharset();
-			broadcastXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-			broadcastXml.append("<Notify>\r\n");
-			broadcastXml.append("<CmdType>Broadcast</CmdType>\r\n");
-			broadcastXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-			broadcastXml.append("<SourceID>" + sipConfig.getId() + "</SourceID>\r\n");
-			broadcastXml.append("<TargetID>" + device.getDeviceId() + "</TargetID>\r\n");
-			broadcastXml.append("</Notify>\r\n");
-
-			CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-					: udpSipProvider.getNewCallId();
-								
-			Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
-			transmitRequest(device, request, errorEvent);
-		} catch (SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-		} 
-	} 
-	
-	
-	/**
-	 * 音视频录像控制
-	 * 
-	 * @param device		视频设备
-	 * @param channelId  	预览通道
-	 * @param recordCmdStr	录像命令:Record / StopRecord
-	 */  
-	@Override
-	public boolean recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent) {
-		try {
-			StringBuffer cmdXml = new StringBuffer(200);
-			String charset = device.getCharset();
-			cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-			cmdXml.append("<Control>\r\n");
-			cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
-			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-			if (ObjectUtils.isEmpty(channelId)) {
-				cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-			} else {
-				cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
-			}
-			cmdXml.append("<RecordCmd>" + recordCmdStr + "</RecordCmd>\r\n");
-			cmdXml.append("</Control>\r\n");
-
-			CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-					: udpSipProvider.getNewCallId();
-
-			Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
-			transmitRequest(device, request, errorEvent);
-			return true;
-		} catch (SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-			return false;
-		} 
-	}
-
-	/**
-	 * 远程启动控制命令
-	 * 
-	 * @param device	视频设备
-	 */
-	@Override
-	public boolean teleBootCmd(Device device) {
-		try {
-			StringBuffer cmdXml = new StringBuffer(200);
-			String charset = device.getCharset();
-			cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-			cmdXml.append("<Control>\r\n");
-			cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
-			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-			cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-			cmdXml.append("<TeleBoot>Boot</TeleBoot>\r\n");
-			cmdXml.append("</Control>\r\n");
-
-			CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-					: udpSipProvider.getNewCallId();
-
-			Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
-			transmitRequest(device, request);
-			return true;
-		} catch (SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-			return false;
-		} 
-	}
-	
-	/**
-	 * 报警布防/撤防命令
-	 * 
-	 * @param device  		视频设备
-	 * @param guardCmdStr	"SetGuard"/"ResetGuard"
-	 */
-	@Override
-	public boolean guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent) {
-		try {
-			StringBuffer cmdXml = new StringBuffer(200);
-			String charset = device.getCharset();
-			cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-			cmdXml.append("<Control>\r\n");
-			cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
-			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-			cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-			cmdXml.append("<GuardCmd>" + guardCmdStr + "</GuardCmd>\r\n");
-			cmdXml.append("</Control>\r\n");
-
-			CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-					: udpSipProvider.getNewCallId();
-
-			Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
-			transmitRequest(device, request, errorEvent);
-			return true;
-		} catch (SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-			return false;
-		} 
-	}
-
-	/**
-	 * 报警复位命令
-	 * 
-	 * @param device  视频设备
-	 */  
-	@Override
-	public boolean alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent) {
-		try {
-			StringBuffer cmdXml = new StringBuffer(200);
-			String charset = device.getCharset();
-			cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-			cmdXml.append("<Control>\r\n");
-			cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
-			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-			cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-			cmdXml.append("<AlarmCmd>ResetAlarm</AlarmCmd>\r\n");
-			if (!ObjectUtils.isEmpty(alarmMethod) || !ObjectUtils.isEmpty(alarmType)) {
-				cmdXml.append("<Info>\r\n");
-			}
-			if (!ObjectUtils.isEmpty(alarmMethod)) {
-				cmdXml.append("<AlarmMethod>" + alarmMethod + "</AlarmMethod>\r\n");
-			}
-			if (!ObjectUtils.isEmpty(alarmType)) {
-				cmdXml.append("<AlarmType>" + alarmType + "</AlarmType>\r\n");
-			}
-			if (!ObjectUtils.isEmpty(alarmMethod) || !ObjectUtils.isEmpty(alarmType)) {
-				cmdXml.append("</Info>\r\n");
-			}
-			cmdXml.append("</Control>\r\n");
-
-			CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-					: udpSipProvider.getNewCallId();
-
-			Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
-			transmitRequest(device, request, errorEvent);
-			return true;
-		} catch (SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-			return false;
-		} 
-	}
-
-	/**
-	 * 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧
-	 * 
-	 * @param device  视频设备
-	 * @param channelId  预览通道
-	 */ 
-	@Override
-	public boolean iFrameCmd(Device device, String channelId) {
-		try {
-			StringBuffer cmdXml = new StringBuffer(200);
-			String charset = device.getCharset();
-			cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-			cmdXml.append("<Control>\r\n");
-			cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
-			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-			if (ObjectUtils.isEmpty(channelId)) {
-				cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-			} else {
-				cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
-			}
-			cmdXml.append("<IFameCmd>Send</IFameCmd>\r\n");
-			cmdXml.append("</Control>\r\n");
-
-			CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-					: udpSipProvider.getNewCallId();
-
-			Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
-			transmitRequest(device, request);
-			return true;
-		} catch (SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-			return false;
-		} 
-	}
-
-	/**
-	 * 看守位控制命令
-	 * 
-	 * @param device		视频设备
-	 * @param enabled		看守位使能:1 = 开启,0 = 关闭
-	 * @param resetTime		自动归位时间间隔,开启看守位时使用,单位:秒(s)
-	 * @param presetIndex	调用预置位编号,开启看守位时使用,取值范围0~255
-	 */  
-	@Override
-	public boolean homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent) {
-		try {
-			StringBuffer cmdXml = new StringBuffer(200);
-			String charset = device.getCharset();
-			cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-			cmdXml.append("<Control>\r\n");
-			cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
-			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-			if (ObjectUtils.isEmpty(channelId)) {
-				cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-			} else {
-				cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
-			}
-			cmdXml.append("<HomePosition>\r\n");
-			if (NumericUtil.isInteger(enabled) && (!enabled.equals("0"))) {
-				cmdXml.append("<Enabled>1</Enabled>\r\n");
-				if (NumericUtil.isInteger(resetTime)) {
-					cmdXml.append("<ResetTime>" + resetTime + "</ResetTime>\r\n");
-				} else {
-					cmdXml.append("<ResetTime>0</ResetTime>\r\n");
-				}
-				if (NumericUtil.isInteger(presetIndex)) {
-					cmdXml.append("<PresetIndex>" + presetIndex + "</PresetIndex>\r\n");
-				} else {
-					cmdXml.append("<PresetIndex>0</PresetIndex>\r\n");
-				}
-			} else {
-				cmdXml.append("<Enabled>0</Enabled>\r\n");
-			}
-			cmdXml.append("</HomePosition>\r\n");
-			cmdXml.append("</Control>\r\n");
-
-			CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-					: udpSipProvider.getNewCallId();
-
-			Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
-			transmitRequest(device, request, errorEvent);
-			return true;
-		} catch (SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-			return false;
-		} 
-	}
-
-	/**
-	 * 设备配置命令
-	 * 
-	 * @param device  视频设备
-	 */  
-	@Override
-	public boolean deviceConfigCmd(Device device) {
-		// TODO Auto-generated method stub
-		return false;
-	}
-
-	/**
-	 * 设备配置命令:basicParam
-	 * 
-	 * @param device  			视频设备
-	 * @param channelId			通道编码(可选)
-	 * @param name				设备/通道名称(可选)
-	 * @param expiration		注册过期时间(可选)
-	 * @param heartBeatInterval	心跳间隔时间(可选)
-	 * @param heartBeatCount	心跳超时次数(可选)
-	 */  
-	@Override
-	public boolean deviceBasicConfigCmd(Device device, String channelId, String name, String expiration, 
-										String heartBeatInterval, String heartBeatCount, SipSubscribe.Event errorEvent) {
-		try {
-			StringBuffer cmdXml = new StringBuffer(200);
-			String charset = device.getCharset();
-			cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-			cmdXml.append("<Control>\r\n");
-			cmdXml.append("<CmdType>DeviceConfig</CmdType>\r\n");
-			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-			if (ObjectUtils.isEmpty(channelId)) {
-				cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-			} else {
-				cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
-			}
-			cmdXml.append("<BasicParam>\r\n");
-			if (!ObjectUtils.isEmpty(name)) {
-				cmdXml.append("<Name>" + name + "</Name>\r\n");
-			}
-			if (NumericUtil.isInteger(expiration)) {
-				if (Integer.valueOf(expiration) > 0) {
-					cmdXml.append("<Expiration>" + expiration + "</Expiration>\r\n");
-				}
-			}
-			if (NumericUtil.isInteger(heartBeatInterval)) {
-				if (Integer.valueOf(heartBeatInterval) > 0) {
-					cmdXml.append("<HeartBeatInterval>" + heartBeatInterval + "</HeartBeatInterval>\r\n");
-				}
-			}
-			if (NumericUtil.isInteger(heartBeatCount)) {
-				if (Integer.valueOf(heartBeatCount) > 0) {
-					cmdXml.append("<HeartBeatCount>" + heartBeatCount + "</HeartBeatCount>\r\n");
-				}
-			}
-			cmdXml.append("</BasicParam>\r\n");
-			cmdXml.append("</Control>\r\n");
-
-			CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-					: udpSipProvider.getNewCallId();
-
-			Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
-			transmitRequest(device, request, errorEvent);
-			return true;
-		} catch (SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-			return false;
-		} 
-	}
-
-	/**
-	 * 查询设备状态
-	 * 
-	 * @param device 视频设备
-	 */  
-	@Override
-	public boolean deviceStatusQuery(Device device, SipSubscribe.Event errorEvent) {
-		try {
-			String charset = device.getCharset();
-			StringBuffer catalogXml = new StringBuffer(200);
-			catalogXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-			catalogXml.append("<Query>\r\n");
-			catalogXml.append("<CmdType>DeviceStatus</CmdType>\r\n");
-			catalogXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-			catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-			catalogXml.append("</Query>\r\n");
-
-			CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-					: udpSipProvider.getNewCallId();
-
-			Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
-
-			transmitRequest(device, request, errorEvent);
-			return true;
-			
-		} catch (SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-			return false;
-		}
-	}
-
-	/**
-	 * 查询设备信息
-	 * 
-	 * @param device 视频设备
-	 */  
-	@Override
-	public boolean deviceInfoQuery(Device device) {
-		try {
-			StringBuffer catalogXml = new StringBuffer(200);
-			String charset = device.getCharset();
-			catalogXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-			catalogXml.append("<Query>\r\n");
-			catalogXml.append("<CmdType>DeviceInfo</CmdType>\r\n");
-			catalogXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-			catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-			catalogXml.append("</Query>\r\n");
-
-			CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-					: udpSipProvider.getNewCallId();
-
-			Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
-
-			transmitRequest(device, request);
-			
-		} catch (SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-			return false;
-		}
-		return true;
-	}
-
-	/**
-	 * 查询目录列表
-	 * 
-	 * @param device 视频设备
-	 */ 
-	@Override
-	public boolean catalogQuery(Device device, int sn, SipSubscribe.Event errorEvent) {
-		try {
-			StringBuffer catalogXml = new StringBuffer(200);
-			String charset = device.getCharset();
-			catalogXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-			catalogXml.append("<Query>\r\n");
-			catalogXml.append("  <CmdType>Catalog</CmdType>\r\n");
-			catalogXml.append("  <SN>" + sn + "</SN>\r\n");
-			catalogXml.append("  <DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-			catalogXml.append("</Query>\r\n");
-
-			CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-					: udpSipProvider.getNewCallId();
-
-			Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), SipUtils.getNewViaTag(),  SipUtils.getNewFromTag(), null, callIdHeader);
-
-			transmitRequest(device, request, errorEvent);
-		} catch (SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-			return false;
-		}
-		return true;
-	}
-
-	/**
-	 * 查询录像信息
-	 * 
-	 * @param device 视频设备
-	 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
-	 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
-	 */  
-	@Override
-	public boolean recordInfoQuery(Device device, String channelId, String startTime, String endTime, int sn, Integer secrecy, String type, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) {
-		if (secrecy == null) {
-			secrecy = 0;
-		}
-		if (type == null) {
-			type = "all";
-		}
-		try {
-			StringBuffer recordInfoXml = new StringBuffer(200);
-			String charset = device.getCharset();
-			recordInfoXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-			recordInfoXml.append("<Query>\r\n");
-			recordInfoXml.append("<CmdType>RecordInfo</CmdType>\r\n");
-			recordInfoXml.append("<SN>" + sn + "</SN>\r\n");
-			recordInfoXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
-			if (startTime != null) {
-				recordInfoXml.append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(startTime) + "</StartTime>\r\n");
-			}
-			if (endTime != null) {
-				recordInfoXml.append("<EndTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(endTime) + "</EndTime>\r\n");
-			}
-			if (secrecy != null) {
-				recordInfoXml.append("<Secrecy> "+ secrecy + " </Secrecy>\r\n");
-			}
-			if (type != null) {
-				// 大华NVR要求必须增加一个值为all的文本元素节点Type
-				recordInfoXml.append("<Type>" + type+"</Type>\r\n");
-			}
-			recordInfoXml.append("</Query>\r\n");
-			
-			CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-					: udpSipProvider.getNewCallId();
-
-			Request request = headerProvider.createMessageRequest(device, recordInfoXml.toString(),
-					SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
-
-			transmitRequest(device, request, errorEvent, okEvent);
-		} catch (SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-			return false;
-		}
-		return true;
-	}
-
-	/**
-	 * 查询报警信息
-	 * 
-	 * @param device		视频设备
-	 * @param startPriority	报警起始级别(可选)
-	 * @param endPriority	报警终止级别(可选)
-	 * @param alarmMethod	报警方式条件(可选)
-	 * @param alarmType		报警类型
-	 * @param startTime		报警发生起始时间(可选)
-	 * @param endTime		报警发生终止时间(可选)
-	 * @return				true = 命令发送成功
-	 */
-	@Override
-	public boolean alarmInfoQuery(Device device, String startPriority, String endPriority, String alarmMethod, String alarmType,
-								 String startTime, String endTime, SipSubscribe.Event errorEvent) {
-		try {
-			StringBuffer cmdXml = new StringBuffer(200);
-			String charset = device.getCharset();
-			cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-			cmdXml.append("<Query>\r\n");
-			cmdXml.append("<CmdType>Alarm</CmdType>\r\n");
-			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-			cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-			if (!ObjectUtils.isEmpty(startPriority)) {
-				cmdXml.append("<StartAlarmPriority>" + startPriority + "</StartAlarmPriority>\r\n");
-			}
-			if (!ObjectUtils.isEmpty(endPriority)) {
-				cmdXml.append("<EndAlarmPriority>" + endPriority + "</EndAlarmPriority>\r\n");
-			}
-			if (!ObjectUtils.isEmpty(alarmMethod)) {
-				cmdXml.append("<AlarmMethod>" + alarmMethod + "</AlarmMethod>\r\n");
-			}
-			if (!ObjectUtils.isEmpty(alarmType)) {
-				cmdXml.append("<AlarmType>" + alarmType + "</AlarmType>\r\n");
-			}
-			if (!ObjectUtils.isEmpty(startTime)) {
-				cmdXml.append("<StartAlarmTime>" + startTime + "</StartAlarmTime>\r\n");
-			}
-			if (!ObjectUtils.isEmpty(endTime)) {
-				cmdXml.append("<EndAlarmTime>" + endTime + "</EndAlarmTime>\r\n");
-			}
-			cmdXml.append("</Query>\r\n");
-			
-			CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-					: udpSipProvider.getNewCallId();
-
-			Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
-			transmitRequest(device, request, errorEvent);
-			return true;
-		} catch (SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-			return false;
-		} 
-	}
-
-	/**
-	 * 查询设备配置
-	 * 
-	 * @param device 		视频设备
-	 * @param channelId		通道编码(可选)
-	 * @param configType	配置类型:
-	 */
-	@Override
-	public boolean deviceConfigQuery(Device device, String channelId, String configType,  SipSubscribe.Event errorEvent) {
-		try {
-			StringBuffer cmdXml = new StringBuffer(200);
-			String charset = device.getCharset();
-			cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-			cmdXml.append("<Query>\r\n");
-			cmdXml.append("<CmdType>ConfigDownload</CmdType>\r\n");
-			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-			if (ObjectUtils.isEmpty(channelId)) {
-				cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-			} else {
-				cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
-			}
-			cmdXml.append("<ConfigType>" + configType + "</ConfigType>\r\n");
-			cmdXml.append("</Query>\r\n");
-			
-			CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-					: udpSipProvider.getNewCallId();
-
-			Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
-			transmitRequest(device, request, errorEvent);
-			return true;
-		} catch (SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-			return false;
-		} 
-	}
-
-	/**
-	 * 查询设备预置位置
-	 * 
-	 * @param device 视频设备
-	 */  
-	@Override
-	public boolean presetQuery(Device device, String channelId, SipSubscribe.Event errorEvent) {
-		try {
-			StringBuffer cmdXml = new StringBuffer(200);
-			String charset = device.getCharset();
-			cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-			cmdXml.append("<Query>\r\n");
-			cmdXml.append("<CmdType>PresetQuery</CmdType>\r\n");
-			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-			if (ObjectUtils.isEmpty(channelId)) {
-				cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-			} else {
-				cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
-			}
-			cmdXml.append("</Query>\r\n");
-			
-			CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-					: udpSipProvider.getNewCallId();
-
-			Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
-			transmitRequest(device, request, errorEvent);
-			return true;
-		} catch (SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-			return false;
-		} 
-	}
-
-	/**
-	 * 查询移动设备位置数据
-	 * 
-	 * @param device 视频设备
-	 */  
-	@Override
-	public boolean mobilePostitionQuery(Device device, SipSubscribe.Event errorEvent) {
-		try {
-			StringBuffer mobilePostitionXml = new StringBuffer(200);
-			String charset = device.getCharset();
-			mobilePostitionXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-			mobilePostitionXml.append("<Query>\r\n");
-			mobilePostitionXml.append("<CmdType>MobilePosition</CmdType>\r\n");
-			mobilePostitionXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-			mobilePostitionXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-			mobilePostitionXml.append("<Interval>60</Interval>\r\n");
-			mobilePostitionXml.append("</Query>\r\n");
-			
-			CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-					: udpSipProvider.getNewCallId();
-
-			Request request = headerProvider.createMessageRequest(device, mobilePostitionXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
-
-			transmitRequest(device, request, errorEvent);
-			
-		} catch (SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-			return false;
-		}
-		return true;
-	}
-
-	/**
-	 * 订阅、取消订阅移动位置
-	 * 
-	 * @param device	视频设备
-	 * @return			true = 命令发送成功
-	 */
-	@Override
-	public SIPRequest mobilePositionSubscribe(Device device, SIPRequest requestOld, SipSubscribe.Event okEvent ,SipSubscribe.Event errorEvent) {
-		try {
-			StringBuffer subscribePostitionXml = new StringBuffer(200);
-			String charset = device.getCharset();
-			subscribePostitionXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-			subscribePostitionXml.append("<Query>\r\n");
-			subscribePostitionXml.append("<CmdType>MobilePosition</CmdType>\r\n");
-			subscribePostitionXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-			subscribePostitionXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-			if (device.getSubscribeCycleForMobilePosition() > 0) {
-				subscribePostitionXml.append("<Interval>" + device.getMobilePositionSubmissionInterval() + "</Interval>\r\n");
-			}
-			subscribePostitionXml.append("</Query>\r\n");
-
-			CallIdHeader callIdHeader;
-
-			if (requestOld != null) {
-				callIdHeader = sipFactory.createHeaderFactory().createCallIdHeader(requestOld.getCallIdHeader().getCallId());
-			}else {
-				callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-						: udpSipProvider.getNewCallId();
-			}
-			SIPRequest request = (SIPRequest)headerProvider.createSubscribeRequest(device, subscribePostitionXml.toString(), requestOld, device.getSubscribeCycleForMobilePosition(), "presence" ,callIdHeader); //Position;id=" + tm.substring(tm.length() - 4));
-
-			transmitRequest(device, request, errorEvent, okEvent);
-
-			return request;
-
-		} catch ( NumberFormatException | ParseException | InvalidArgumentException	| SipException e) {
-			e.printStackTrace();
-			return null;
-		}
-	}
-
-	/**
-	 * 订阅、取消订阅报警信息
-	 * 
-	 * @param device		视频设备
-	 * @param expires		订阅过期时间(0 = 取消订阅)
-	 * @param startPriority	报警起始级别(可选)
-	 * @param endPriority	报警终止级别(可选)
-	 * @param alarmMethod	报警方式条件(可选)
-	 * @param alarmType		报警类型
-	 * @param startTime		报警发生起始时间(可选)
-	 * @param endTime		报警发生终止时间(可选)
-	 * @return				true = 命令发送成功
-	 */
-	@Override
-	public boolean alarmSubscribe(Device device, int expires, String startPriority, String endPriority, String alarmMethod, String alarmType, String startTime, String endTime) {
-		try {
-			StringBuffer cmdXml = new StringBuffer(200);
-			String charset = device.getCharset();
-			cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-			cmdXml.append("<Query>\r\n");
-			cmdXml.append("<CmdType>Alarm</CmdType>\r\n");
-			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-			cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-			if (!ObjectUtils.isEmpty(startPriority)) {
-				cmdXml.append("<StartAlarmPriority>" + startPriority + "</StartAlarmPriority>\r\n");
-			}
-			if (!ObjectUtils.isEmpty(endPriority)) {
-				cmdXml.append("<EndAlarmPriority>" + endPriority + "</EndAlarmPriority>\r\n");
-			}
-			if (!ObjectUtils.isEmpty(alarmMethod)) {
-				cmdXml.append("<AlarmMethod>" + alarmMethod + "</AlarmMethod>\r\n");
-			}
-			if (!ObjectUtils.isEmpty(alarmType)) {
-				cmdXml.append("<AlarmType>" + alarmType + "</AlarmType>\r\n");
-			}
-			if (!ObjectUtils.isEmpty(startTime)) {
-				cmdXml.append("<StartAlarmTime>" + startTime + "</StartAlarmTime>\r\n");
-			}
-			if (!ObjectUtils.isEmpty(endTime)) {
-				cmdXml.append("<EndAlarmTime>" + endTime + "</EndAlarmTime>\r\n");
-			}
-			cmdXml.append("</Query>\r\n");
-
-			CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-					: udpSipProvider.getNewCallId();
-
-			Request request = headerProvider.createSubscribeRequest(device, cmdXml.toString(), null, expires, "presence" , callIdHeader);
-			transmitRequest(device, request);
-
-			return true;
-
-		} catch ( NumberFormatException | ParseException | InvalidArgumentException	| SipException e) {
-			e.printStackTrace();
-			return false;
-		}
-	}
-
-	@Override
-	public SIPRequest catalogSubscribe(Device device, SIPRequest requestOld, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) {
-		try {
-			StringBuffer cmdXml = new StringBuffer(200);
-			String charset = device.getCharset();
-			cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-			cmdXml.append("<Query>\r\n");
-			cmdXml.append("<CmdType>Catalog</CmdType>\r\n");
-			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-			cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-			cmdXml.append("</Query>\r\n");
-
-			CallIdHeader callIdHeader ;
-
-			if (requestOld != null) {
-				callIdHeader = sipFactory.createHeaderFactory().createCallIdHeader(requestOld.getCallIdHeader().getCallId());
-			}else {
-				callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-						: udpSipProvider.getNewCallId();
-			}
-
-			// 有效时间默认为60秒以上
-			SIPRequest request = (SIPRequest)headerProvider.createSubscribeRequest(device, cmdXml.toString(), requestOld,  device.getSubscribeCycleForCatalog(), "Catalog" ,
-					callIdHeader);
-			transmitRequest(device, request, errorEvent, okEvent);
-			return request;
-
-		} catch ( NumberFormatException | ParseException | InvalidArgumentException	| SipException e) {
-			e.printStackTrace();
-			return null;
-		}
-	}
-
-	@Override
-	public boolean dragZoomCmd(Device device, String channelId, String cmdString) {
-		try {
-			StringBuffer dragXml = new StringBuffer(200);
-			String charset = device.getCharset();
-			dragXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-			dragXml.append("<Control>\r\n");
-			dragXml.append("<CmdType>DeviceControl</CmdType>\r\n");
-			dragXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-			if (ObjectUtils.isEmpty(channelId)) {
-				dragXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-			} else {
-				dragXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
-			}
-			dragXml.append(cmdString);
-			dragXml.append("</Control>\r\n");
-			CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-					: udpSipProvider.getNewCallId();
-			Request request = headerProvider.createMessageRequest(device, dragXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
-			logger.debug("拉框信令: " + request.toString());
-			transmitRequest(device, request);
-			return true;
-		} catch (SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-		}
-		return false;
-	}
-
-
-	private ClientTransaction transmitRequest(Device device, Request request) throws SipException {
-		return transmitRequest(device, request, null, null);
-	}
-
-	private ClientTransaction transmitRequest(Device device, Request request, SipSubscribe.Event errorEvent) throws SipException {
-		return transmitRequest(device, request, errorEvent, null);
-	}
-
-	private ClientTransaction transmitRequest(Device device, Request request, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws SipException {
-		ClientTransaction clientTransaction = null;
-		if("TCP".equals(device.getTransport())) {
-			clientTransaction = tcpSipProvider.getNewClientTransaction(request);
-		} else if("UDP".equals(device.getTransport())) {
-			clientTransaction = udpSipProvider.getNewClientTransaction(request);
-		}
-		if (request.getHeader(UserAgentHeader.NAME) == null) {
-			try {
-				request.addHeader(SipUtils.createUserAgentHeader(sipFactory, gitUtil));
-			} catch (ParseException e) {
-				logger.error("添加UserAgentHeader失败", e);
-			}
-		}
-		CallIdHeader callIdHeader = (CallIdHeader)request.getHeader(CallIdHeader.NAME);
-		// 添加错误订阅
-		if (errorEvent != null) {
-			sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), (eventResult -> {
-				errorEvent.response(eventResult);
-				sipSubscribe.removeErrorSubscribe(eventResult.callId);
-				sipSubscribe.removeOkSubscribe(eventResult.callId);
-			}));
-		}
-		// 添加订阅
-		if (okEvent != null) {
-			sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), eventResult ->{
-				okEvent.response(eventResult);
-				sipSubscribe.removeOkSubscribe(eventResult.callId);
-				sipSubscribe.removeErrorSubscribe(eventResult.callId);
-			});
-		}
-
-		clientTransaction.sendRequest();
-		return clientTransaction;
-	}
-
-	/**
-	 * 回放暂停
-	 */
-	@Override
-	public void playPauseCmd(Device device, StreamInfo streamInfo) {
-		try {
-			StringBuffer content = new StringBuffer(200);
-			content.append("PAUSE RTSP/1.0\r\n");
-			content.append("CSeq: " + getInfoCseq() + "\r\n");
-			content.append("PauseTime: now\r\n");
-			Request request = headerProvider.createInfoRequest(device, streamInfo, content.toString());
-			if (request == null) {
-				return;
-			}
-			logger.info(request.toString());
-			ClientTransaction clientTransaction = null;
-			if ("TCP".equals(device.getTransport())) {
-				clientTransaction = tcpSipProvider.getNewClientTransaction(request);
-			} else if ("UDP".equals(device.getTransport())) {
-				clientTransaction = udpSipProvider.getNewClientTransaction(request);
-			}
-			if (clientTransaction != null) {
-				clientTransaction.sendRequest();
-			}
-
-		} catch (SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-		}
-	}
-
-	/**
-	 * 回放恢复
-	 */
-	@Override
-	public void playResumeCmd(Device device, StreamInfo streamInfo) {
-		try {
-			StringBuffer content = new StringBuffer(200);
-			content.append("PLAY RTSP/1.0\r\n");
-			content.append("CSeq: " + getInfoCseq() + "\r\n");
-			content.append("Range: npt=now-\r\n");
-			Request request = headerProvider.createInfoRequest(device, streamInfo, content.toString());
-			if (request == null) {
-				return;
-			}
-			logger.info(request.toString());
-			ClientTransaction clientTransaction = null;
-			if ("TCP".equals(device.getTransport())) {
-				clientTransaction = tcpSipProvider.getNewClientTransaction(request);
-			} else if ("UDP".equals(device.getTransport())) {
-				clientTransaction = udpSipProvider.getNewClientTransaction(request);
-			}
-
-			clientTransaction.sendRequest();
-
-		} catch (SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-		}
-	}
-
-	/**
-	 * 回放拖动播放
-	 */
-	@Override
-	public void playSeekCmd(Device device, StreamInfo streamInfo, long seekTime) {
-		try {
-			StringBuffer content = new StringBuffer(200);
-			content.append("PLAY RTSP/1.0\r\n");
-			content.append("CSeq: " + getInfoCseq() + "\r\n");
-			content.append("Range: npt=" + Math.abs(seekTime) + "-\r\n");
-
-			Request request = headerProvider.createInfoRequest(device, streamInfo, content.toString());
-			if (request == null) {
-				return;
-			}
-			logger.info(request.toString());
-			ClientTransaction clientTransaction = null;
-			if ("TCP".equals(device.getTransport())) {
-				clientTransaction = tcpSipProvider.getNewClientTransaction(request);
-			} else if ("UDP".equals(device.getTransport())) {
-				clientTransaction = udpSipProvider.getNewClientTransaction(request);
-			}
-
-			clientTransaction.sendRequest();
-
-		} catch (SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-		}
-	}
-
-	/**
-	 * 回放倍速播放
-	 */
-	@Override
-	public void playSpeedCmd(Device device, StreamInfo streamInfo, Double speed) {
-		try {
-
-			StringBuffer content = new StringBuffer(200);
-			content.append("PLAY RTSP/1.0\r\n");
-			content.append("CSeq: " + getInfoCseq() + "\r\n");
-			content.append("Scale: " + String.format("%.6f",speed) + "\r\n");
-			Request request = headerProvider.createInfoRequest(device, streamInfo, content.toString());
-			if (request == null) {
-				return;
-			}
-			logger.info(request.toString());
-			ClientTransaction clientTransaction = null;
-			if ("TCP".equals(device.getTransport())) {
-				clientTransaction = tcpSipProvider.getNewClientTransaction(request);
-			} else if ("UDP".equals(device.getTransport())) {
-				clientTransaction = udpSipProvider.getNewClientTransaction(request);
-			}
-
-			clientTransaction.sendRequest();
-
-		} catch (SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-		}
-	}
-
-	private int getInfoCseq() {
-		return (int) ((Math.random() * 9 + 1) * Math.pow(10, 8));
-	}
-	
-	@Override
-	public void playbackControlCmd(Device device, StreamInfo streamInfo, String content,SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) {
-		try {
-			Request request = headerProvider.createInfoRequest(device, streamInfo, content);
-			if (request == null) {
-				return;
-			}
-			ClientTransaction clientTransaction = null;
-			if ("TCP".equals(device.getTransport())) {
-				clientTransaction = tcpSipProvider.getNewClientTransaction(request);
-			} else if ("UDP".equals(device.getTransport())) {
-				clientTransaction = udpSipProvider.getNewClientTransaction(request);
-			}
-			CallIdHeader callIdHeader = (CallIdHeader)request.getHeader(CallIdHeader.NAME);
-			if(errorEvent != null) {
-				sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), (eventResult -> {
-					errorEvent.response(eventResult);
-					sipSubscribe.removeErrorSubscribe(eventResult.callId);
-					sipSubscribe.removeOkSubscribe(eventResult.callId);
-				}));
-			}
-			
-			if(okEvent != null) {
-				sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), eventResult -> {
-					okEvent.response(eventResult);
-					sipSubscribe.removeOkSubscribe(eventResult.callId);
-					sipSubscribe.removeErrorSubscribe(eventResult.callId);
-				});
-			}
-			clientTransaction.sendRequest();
-			
-		} catch (SipException | ParseException | InvalidArgumentException e) {
-			e.printStackTrace();
-		}
-	}
-
-	@Override
-	public boolean sendAlarmMessage(Device device, DeviceAlarm deviceAlarm) {
-		if (device == null) {
-			return false;
-		}
-		logger.info("[发送 报警通知] {}/{}->{},{}", device.getDeviceId(), deviceAlarm.getChannelId(),
-				deviceAlarm.getLongitude(), deviceAlarm.getLatitude());
-		try {
-			String characterSet = device.getCharset();
-			StringBuffer deviceStatusXml = new StringBuffer(600);
-			deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-			deviceStatusXml.append("<Notify>\r\n");
-			deviceStatusXml.append("<CmdType>Alarm</CmdType>\r\n");
-			deviceStatusXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-			deviceStatusXml.append("<DeviceID>" + deviceAlarm.getChannelId() + "</DeviceID>\r\n");
-			deviceStatusXml.append("<AlarmPriority>" + deviceAlarm.getAlarmPriority() + "</AlarmPriority>\r\n");
-			deviceStatusXml.append("<AlarmMethod>" + deviceAlarm.getAlarmMethod() + "</AlarmMethod>\r\n");
-			deviceStatusXml.append("<AlarmTime>" + deviceAlarm.getAlarmTime() + "</AlarmTime>\r\n");
-			deviceStatusXml.append("<AlarmDescription>" + deviceAlarm.getAlarmDescription() + "</AlarmDescription>\r\n");
-			deviceStatusXml.append("<Longitude>" + deviceAlarm.getLongitude() + "</Longitude>\r\n");
-			deviceStatusXml.append("<Latitude>" + deviceAlarm.getLatitude() + "</Latitude>\r\n");
-			deviceStatusXml.append("<info>\r\n");
-			deviceStatusXml.append("<AlarmType>" + deviceAlarm.getAlarmType() + "</AlarmType>\r\n");
-			deviceStatusXml.append("</info>\r\n");
-			deviceStatusXml.append("</Notify>\r\n");
-
-			CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-					: udpSipProvider.getNewCallId();
-			Request request = headerProvider.createMessageRequest(device, deviceStatusXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
-			transmitRequest(device, request);
-
-
-		} catch (SipException | ParseException  e) {
-			e.printStackTrace();
-			return false;
-		} catch (InvalidArgumentException e) {
-			throw new RuntimeException(e);
-		}
-		return true;
-	}
+        CallIdHeader callIdHeader = device.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+
+        Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), null, SipUtils.getNewFromTag(), null, ssrcInfo.getSsrc(), callIdHeader);
+        transmitRequest(device.getTransport(), request, (e -> {
+            streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
+            mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
+            errorEvent.response(e);
+        }), e -> {
+            // 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值
+            ResponseEvent responseEvent = (ResponseEvent) e.event;
+            SIPResponse response = (SIPResponse) responseEvent.getResponse();
+            streamSession.put(device.getDeviceId(), channelId, "play", stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.play);
+            okEvent.response(e);
+        });
+    }
+
+    /**
+     * 请求回放视频流
+     *
+     * @param device    视频设备
+     * @param channelId 预览通道
+     * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
+     * @param endTime   结束时间,格式要求:yyyy-MM-dd HH:mm:ss
+     */
+    @Override
+    public void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
+                                  String startTime, String endTime, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
+                                  SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+
+
+        logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
+
+        StringBuffer content = new StringBuffer(200);
+        content.append("v=0\r\n");
+        content.append("o=" + channelId + " 0 0 IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
+        content.append("s=Playback\r\n");
+        content.append("u=" + channelId + ":0\r\n");
+        content.append("c=IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
+        content.append("t=" + DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime) + " "
+                + DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime) + "\r\n");
+
+        String streamMode = device.getStreamMode();
+
+        if (userSetting.isSeniorSdp()) {
+            if ("TCP-PASSIVE".equalsIgnoreCase(streamMode)) {
+                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
+            } else if ("TCP-ACTIVE".equalsIgnoreCase(streamMode)) {
+                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
+            } else if ("UDP".equalsIgnoreCase(streamMode)) {
+                content.append("m=video " + ssrcInfo.getPort() + " RTP/AVP 96 126 125 99 34 98 97\r\n");
+            }
+            content.append("a=recvonly\r\n");
+            content.append("a=rtpmap:96 PS/90000\r\n");
+            content.append("a=fmtp:126 profile-level-id=42e01e\r\n");
+            content.append("a=rtpmap:126 H264/90000\r\n");
+            content.append("a=rtpmap:125 H264S/90000\r\n");
+            content.append("a=fmtp:125 profile-level-id=42e01e\r\n");
+            content.append("a=rtpmap:99 H265/90000\r\n");
+            content.append("a=rtpmap:98 H264/90000\r\n");
+            content.append("a=rtpmap:97 MPEG4/90000\r\n");
+            if ("TCP-PASSIVE".equalsIgnoreCase(streamMode)) { // tcp被动模式
+                content.append("a=setup:passive\r\n");
+                content.append("a=connection:new\r\n");
+            } else if ("TCP-ACTIVE".equalsIgnoreCase(streamMode)) { // tcp主动模式
+                content.append("a=setup:active\r\n");
+                content.append("a=connection:new\r\n");
+            }
+        } else {
+            if ("TCP-PASSIVE".equalsIgnoreCase(streamMode)) {
+                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 97 98 99\r\n");
+            } else if ("TCP-ACTIVE".equalsIgnoreCase(streamMode)) {
+                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 97 98 99\r\n");
+            } else if ("UDP".equalsIgnoreCase(streamMode)) {
+                content.append("m=video " + ssrcInfo.getPort() + " RTP/AVP 96 97 98 99\r\n");
+            }
+            content.append("a=recvonly\r\n");
+            content.append("a=rtpmap:96 PS/90000\r\n");
+            content.append("a=rtpmap:97 MPEG4/90000\r\n");
+            content.append("a=rtpmap:98 H264/90000\r\n");
+            content.append("a=rtpmap:99 H265/90000\r\n");
+            if ("TCP-PASSIVE".equalsIgnoreCase(streamMode)) { // tcp被动模式
+                content.append("a=setup:passive\r\n");
+                content.append("a=connection:new\r\n");
+            } else if ("TCP-ACTIVE".equalsIgnoreCase(streamMode)) { // tcp主动模式
+                content.append("a=setup:active\r\n");
+                content.append("a=connection:new\r\n");
+            }
+        }
+
+        content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc
+
+        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+        HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
+        // 添加订阅
+        subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json) -> {
+            if (hookEvent != null) {
+                InviteStreamInfo inviteStreamInfo = new InviteStreamInfo(mediaServerItemInUse, json, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream());
+                hookEvent.call(inviteStreamInfo);
+            }
+            subscribe.removeSubscribe(hookSubscribe);
+        });
+        Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader, ssrcInfo.getSsrc());
+
+        transmitRequest(device.getTransport(), request, errorEvent, event -> {
+            ResponseEvent responseEvent = (ResponseEvent) event.event;
+            SIPResponse response = (SIPResponse) responseEvent.getResponse();
+            streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.playback);
+            okEvent.response(event);
+        });
+        if (inviteStreamCallback != null) {
+            inviteStreamCallback.call(new InviteStreamInfo(mediaServerItem, null, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream()));
+        }
+    }
+
+    /**
+     * 请求历史媒体下载
+     *
+     * @param device        视频设备
+     * @param channelId     预览通道
+     * @param startTime     开始时间,格式要求:yyyy-MM-dd HH:mm:ss
+     * @param endTime       结束时间,格式要求:yyyy-MM-dd HH:mm:ss
+     * @param downloadSpeed 下载倍速参数
+     */
+    @Override
+    public void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
+                                  String startTime, String endTime, int downloadSpeed, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
+                                  SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+
+        logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
+
+        StringBuffer content = new StringBuffer(200);
+        content.append("v=0\r\n");
+        content.append("o=" + channelId + " 0 0 IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
+        content.append("s=Download\r\n");
+        content.append("u=" + channelId + ":0\r\n");
+        content.append("c=IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
+        content.append("t=" + DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime) + " "
+                + DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime) + "\r\n");
+
+        String streamMode = device.getStreamMode().toUpperCase();
+
+        if (userSetting.isSeniorSdp()) {
+            if ("TCP-PASSIVE".equals(streamMode)) {
+                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
+            } else if ("TCP-ACTIVE".equals(streamMode)) {
+                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
+            } else if ("UDP".equals(streamMode)) {
+                content.append("m=video " + ssrcInfo.getPort() + " RTP/AVP 96 126 125 99 34 98 97\r\n");
+            }
+            content.append("a=recvonly\r\n");
+            content.append("a=rtpmap:96 PS/90000\r\n");
+            content.append("a=fmtp:126 profile-level-id=42e01e\r\n");
+            content.append("a=rtpmap:126 H264/90000\r\n");
+            content.append("a=rtpmap:125 H264S/90000\r\n");
+            content.append("a=fmtp:125 profile-level-id=42e01e\r\n");
+            content.append("a=rtpmap:99 MP4V-ES/90000\r\n");
+            content.append("a=fmtp:99 profile-level-id=3\r\n");
+            content.append("a=rtpmap:98 H264/90000\r\n");
+            content.append("a=rtpmap:97 MPEG4/90000\r\n");
+            if ("TCP-PASSIVE".equals(streamMode)) { // tcp被动模式
+                content.append("a=setup:passive\r\n");
+                content.append("a=connection:new\r\n");
+            } else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式
+                content.append("a=setup:active\r\n");
+                content.append("a=connection:new\r\n");
+            }
+        } else {
+            if ("TCP-PASSIVE".equals(streamMode)) {
+                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 97 98 99\r\n");
+            } else if ("TCP-ACTIVE".equals(streamMode)) {
+                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 97 98 99\r\n");
+            } else if ("UDP".equals(streamMode)) {
+                content.append("m=video " + ssrcInfo.getPort() + " RTP/AVP 96 97 98 99\r\n");
+            }
+            content.append("a=recvonly\r\n");
+            content.append("a=rtpmap:96 PS/90000\r\n");
+            content.append("a=rtpmap:97 MPEG4/90000\r\n");
+            content.append("a=rtpmap:98 H264/90000\r\n");
+            content.append("a=rtpmap:99 H265/90000\r\n");
+            if ("TCP-PASSIVE".equals(streamMode)) { // tcp被动模式
+                content.append("a=setup:passive\r\n");
+                content.append("a=connection:new\r\n");
+            } else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式
+                content.append("a=setup:active\r\n");
+                content.append("a=connection:new\r\n");
+            }
+        }
+        content.append("a=downloadspeed:" + downloadSpeed + "\r\n");
+
+        content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc
+
+        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+
+        HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, null, mediaServerItem.getId());
+        // 添加订阅
+        subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json) -> {
+            hookEvent.call(new InviteStreamInfo(mediaServerItem, json, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream()));
+            subscribe.removeSubscribe(hookSubscribe);
+            hookSubscribe.getContent().put("regist", false);
+            hookSubscribe.getContent().put("schema", "rtsp");
+            // 添加流注销的订阅,注销了后向设备发送bye
+            subscribe.addSubscribe(hookSubscribe,
+                    (MediaServerItem mediaServerItemForEnd, JSONObject jsonForEnd) -> {
+                        logger.info("[录像]下载结束, 发送BYE");
+                        try {
+                            streamByeCmd(device, channelId, ssrcInfo.getStream(), callIdHeader.getCallId());
+                        } catch (InvalidArgumentException | ParseException | SipException |
+                                 SsrcTransactionNotFoundException e) {
+                            logger.error("[录像]下载结束, 发送BYE失败 {}", e.getMessage());
+                        }
+                    });
+        });
+
+        Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader, ssrcInfo.getSsrc());
+        if (inviteStreamCallback != null) {
+            inviteStreamCallback.call(new InviteStreamInfo(mediaServerItem, null, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream()));
+        }
+        transmitRequest(device.getTransport(), request, errorEvent, okEvent -> {
+            ResponseEvent responseEvent = (ResponseEvent) okEvent.event;
+            SIPResponse response = (SIPResponse) responseEvent.getResponse();
+            streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.download);
+        });
+    }
+
+    /**
+     * 视频流停止, 不使用回调
+     */
+    @Override
+    public void streamByeCmd(Device device, String channelId, String stream, String callId) throws InvalidArgumentException, ParseException, SipException, SsrcTransactionNotFoundException {
+        streamByeCmd(device, channelId, stream, callId, null);
+    }
+
+    /**
+     * 视频流停止
+     */
+    @Override
+    public void streamByeCmd(Device device, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException {
+        SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, callId, stream);
+        if (ssrcTransaction == null) {
+            throw new SsrcTransactionNotFoundException(device.getDeviceId(), channelId, callId, stream);
+        }
+
+        mediaServerService.releaseSsrc(ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc());
+        mediaServerService.closeRTPServer(ssrcTransaction.getMediaServerId(), ssrcTransaction.getStream());
+        streamSession.remove(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
+
+        Request byteRequest = headerProvider.createByteRequest(device, channelId, ssrcTransaction.getSipTransactionInfo());
+        transmitRequest(device.getTransport(), byteRequest, null, okEvent);
+    }
+
+    /**
+     * 语音广播
+     *
+     * @param device    视频设备
+     * @param channelId 预览通道
+     */
+    @Override
+    public void audioBroadcastCmd(Device device, String channelId) {
+    }
+
+    /**
+     * 语音广播
+     *
+     * @param device 视频设备
+     */
+    @Override
+    public void audioBroadcastCmd(Device device) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer broadcastXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        broadcastXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        broadcastXml.append("<Notify>\r\n");
+        broadcastXml.append("<CmdType>Broadcast</CmdType>\r\n");
+        broadcastXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        broadcastXml.append("<SourceID>" + sipConfig.getId() + "</SourceID>\r\n");
+        broadcastXml.append("<TargetID>" + device.getDeviceId() + "</TargetID>\r\n");
+        broadcastXml.append("</Notify>\r\n");
+
+        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+
+        Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
+        transmitRequest(device.getTransport(), request);
+
+    }
+
+    @Override
+    public void audioBroadcastCmd(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer broadcastXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        broadcastXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        broadcastXml.append("<Notify>\r\n");
+        broadcastXml.append("<CmdType>Broadcast</CmdType>\r\n");
+        broadcastXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        broadcastXml.append("<SourceID>" + sipConfig.getId() + "</SourceID>\r\n");
+        broadcastXml.append("<TargetID>" + device.getDeviceId() + "</TargetID>\r\n");
+        broadcastXml.append("</Notify>\r\n");
+
+        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+
+        Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
+        transmitRequest(device.getTransport(), request, errorEvent);
+
+    }
+
+
+    /**
+     * 音视频录像控制
+     *
+     * @param device       视频设备
+     * @param channelId    预览通道
+     * @param recordCmdStr 录像命令:Record / StopRecord
+     */
+    @Override
+    public void recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+        StringBuffer cmdXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        cmdXml.append("<Control>\r\n");
+        cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
+        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        if (ObjectUtils.isEmpty(channelId)) {
+            cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        } else {
+            cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
+        }
+        cmdXml.append("<RecordCmd>" + recordCmdStr + "</RecordCmd>\r\n");
+        cmdXml.append("</Control>\r\n");
+
+        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
+        transmitRequest(device.getTransport(), request, errorEvent);
+    }
+
+    /**
+     * 远程启动控制命令
+     *
+     * @param device 视频设备
+     */
+    @Override
+    public void teleBootCmd(Device device) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer cmdXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        cmdXml.append("<Control>\r\n");
+        cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
+        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        cmdXml.append("<TeleBoot>Boot</TeleBoot>\r\n");
+        cmdXml.append("</Control>\r\n");
+
+        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
+        transmitRequest(device.getTransport(), request);
+    }
+
+    /**
+     * 报警布防/撤防命令
+     *
+     * @param device      视频设备
+     * @param guardCmdStr "SetGuard"/"ResetGuard"
+     */
+    @Override
+    public void guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer cmdXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        cmdXml.append("<Control>\r\n");
+        cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
+        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        cmdXml.append("<GuardCmd>" + guardCmdStr + "</GuardCmd>\r\n");
+        cmdXml.append("</Control>\r\n");
+
+        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
+        transmitRequest(device.getTransport(), request, errorEvent);
+    }
+
+    /**
+     * 报警复位命令
+     *
+     * @param device 视频设备
+     */
+    @Override
+    public void alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer cmdXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        cmdXml.append("<Control>\r\n");
+        cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
+        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        cmdXml.append("<AlarmCmd>ResetAlarm</AlarmCmd>\r\n");
+        if (!ObjectUtils.isEmpty(alarmMethod) || !ObjectUtils.isEmpty(alarmType)) {
+            cmdXml.append("<Info>\r\n");
+        }
+        if (!ObjectUtils.isEmpty(alarmMethod)) {
+            cmdXml.append("<AlarmMethod>" + alarmMethod + "</AlarmMethod>\r\n");
+        }
+        if (!ObjectUtils.isEmpty(alarmType)) {
+            cmdXml.append("<AlarmType>" + alarmType + "</AlarmType>\r\n");
+        }
+        if (!ObjectUtils.isEmpty(alarmMethod) || !ObjectUtils.isEmpty(alarmType)) {
+            cmdXml.append("</Info>\r\n");
+        }
+        cmdXml.append("</Control>\r\n");
+
+        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
+        transmitRequest(device.getTransport(), request, errorEvent);
+    }
+
+    /**
+     * 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧
+     *
+     * @param device    视频设备
+     * @param channelId 预览通道
+     */
+    @Override
+    public void iFrameCmd(Device device, String channelId) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer cmdXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        cmdXml.append("<Control>\r\n");
+        cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
+        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        if (ObjectUtils.isEmpty(channelId)) {
+            cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        } else {
+            cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
+        }
+        cmdXml.append("<IFameCmd>Send</IFameCmd>\r\n");
+        cmdXml.append("</Control>\r\n");
+
+        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
+        transmitRequest(device.getTransport(), request);
+    }
+
+    /**
+     * 看守位控制命令
+     *
+     * @param device      视频设备
+     * @param enabled     看守位使能:1 = 开启,0 = 关闭
+     * @param resetTime   自动归位时间间隔,开启看守位时使用,单位:秒(s)
+     * @param presetIndex 调用预置位编号,开启看守位时使用,取值范围0~255
+     */
+    @Override
+    public void homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer cmdXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        cmdXml.append("<Control>\r\n");
+        cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
+        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        if (ObjectUtils.isEmpty(channelId)) {
+            cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        } else {
+            cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
+        }
+        cmdXml.append("<HomePosition>\r\n");
+        if (NumericUtil.isInteger(enabled) && (!enabled.equals("0"))) {
+            cmdXml.append("<Enabled>1</Enabled>\r\n");
+            if (NumericUtil.isInteger(resetTime)) {
+                cmdXml.append("<ResetTime>" + resetTime + "</ResetTime>\r\n");
+            } else {
+                cmdXml.append("<ResetTime>0</ResetTime>\r\n");
+            }
+            if (NumericUtil.isInteger(presetIndex)) {
+                cmdXml.append("<PresetIndex>" + presetIndex + "</PresetIndex>\r\n");
+            } else {
+                cmdXml.append("<PresetIndex>0</PresetIndex>\r\n");
+            }
+        } else {
+            cmdXml.append("<Enabled>0</Enabled>\r\n");
+        }
+        cmdXml.append("</HomePosition>\r\n");
+        cmdXml.append("</Control>\r\n");
+
+        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
+        transmitRequest(device.getTransport(), request, errorEvent);
+    }
+
+    /**
+     * 设备配置命令
+     *
+     * @param device 视频设备
+     */
+    @Override
+    public void deviceConfigCmd(Device device) {
+        // TODO Auto-generated method stub
+    }
+
+    /**
+     * 设备配置命令:basicParam
+     *
+     * @param device            视频设备
+     * @param channelId         通道编码(可选)
+     * @param name              设备/通道名称(可选)
+     * @param expiration        注册过期时间(可选)
+     * @param heartBeatInterval 心跳间隔时间(可选)
+     * @param heartBeatCount    心跳超时次数(可选)
+     */
+    @Override
+    public void deviceBasicConfigCmd(Device device, String channelId, String name, String expiration,
+                                     String heartBeatInterval, String heartBeatCount, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer cmdXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        cmdXml.append("<Control>\r\n");
+        cmdXml.append("<CmdType>DeviceConfig</CmdType>\r\n");
+        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        if (ObjectUtils.isEmpty(channelId)) {
+            cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        } else {
+            cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
+        }
+        cmdXml.append("<BasicParam>\r\n");
+        if (!ObjectUtils.isEmpty(name)) {
+            cmdXml.append("<Name>" + name + "</Name>\r\n");
+        }
+        if (NumericUtil.isInteger(expiration)) {
+            if (Integer.valueOf(expiration) > 0) {
+                cmdXml.append("<Expiration>" + expiration + "</Expiration>\r\n");
+            }
+        }
+        if (NumericUtil.isInteger(heartBeatInterval)) {
+            if (Integer.valueOf(heartBeatInterval) > 0) {
+                cmdXml.append("<HeartBeatInterval>" + heartBeatInterval + "</HeartBeatInterval>\r\n");
+            }
+        }
+        if (NumericUtil.isInteger(heartBeatCount)) {
+            if (Integer.valueOf(heartBeatCount) > 0) {
+                cmdXml.append("<HeartBeatCount>" + heartBeatCount + "</HeartBeatCount>\r\n");
+            }
+        }
+        cmdXml.append("</BasicParam>\r\n");
+        cmdXml.append("</Control>\r\n");
+
+        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
+        transmitRequest(device.getTransport(), request, errorEvent);
+    }
+
+    /**
+     * 查询设备状态
+     *
+     * @param device 视频设备
+     */
+    @Override
+    public void deviceStatusQuery(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+
+        String charset = device.getCharset();
+        StringBuffer catalogXml = new StringBuffer(200);
+        catalogXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        catalogXml.append("<Query>\r\n");
+        catalogXml.append("<CmdType>DeviceStatus</CmdType>\r\n");
+        catalogXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        catalogXml.append("</Query>\r\n");
+
+        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+
+        Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
+
+        transmitRequest(device.getTransport(), request, errorEvent);
+    }
+
+    /**
+     * 查询设备信息
+     *
+     * @param device 视频设备
+     */
+    @Override
+    public void deviceInfoQuery(Device device) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer catalogXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        catalogXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        catalogXml.append("<Query>\r\n");
+        catalogXml.append("<CmdType>DeviceInfo</CmdType>\r\n");
+        catalogXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        catalogXml.append("</Query>\r\n");
+
+        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+
+        Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
+
+        transmitRequest(device.getTransport(), request);
+
+    }
+
+    /**
+     * 查询目录列表
+     *
+     * @param device 视频设备
+     */
+    @Override
+    public void catalogQuery(Device device, int sn, SipSubscribe.Event errorEvent) throws SipException, InvalidArgumentException, ParseException {
+
+        StringBuffer catalogXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        catalogXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        catalogXml.append("<Query>\r\n");
+        catalogXml.append("  <CmdType>Catalog</CmdType>\r\n");
+        catalogXml.append("  <SN>" + sn + "</SN>\r\n");
+        catalogXml.append("  <DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        catalogXml.append("</Query>\r\n");
+
+        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+
+        Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
+
+        transmitRequest(device.getTransport(), request, errorEvent);
+    }
+
+    /**
+     * 查询录像信息
+     *
+     * @param device    视频设备
+     * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
+     * @param endTime   结束时间,格式要求:yyyy-MM-dd HH:mm:ss
+     */
+    @Override
+    public void recordInfoQuery(Device device, String channelId, String startTime, String endTime, int sn, Integer secrecy, String type, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+        if (secrecy == null) {
+            secrecy = 0;
+        }
+        if (type == null) {
+            type = "all";
+        }
+
+        StringBuffer recordInfoXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        recordInfoXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        recordInfoXml.append("<Query>\r\n");
+        recordInfoXml.append("<CmdType>RecordInfo</CmdType>\r\n");
+        recordInfoXml.append("<SN>" + sn + "</SN>\r\n");
+        recordInfoXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
+        if (startTime != null) {
+            recordInfoXml.append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(startTime) + "</StartTime>\r\n");
+        }
+        if (endTime != null) {
+            recordInfoXml.append("<EndTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(endTime) + "</EndTime>\r\n");
+        }
+        if (secrecy != null) {
+            recordInfoXml.append("<Secrecy> " + secrecy + " </Secrecy>\r\n");
+        }
+        if (type != null) {
+            // 大华NVR要求必须增加一个值为all的文本元素节点Type
+            recordInfoXml.append("<Type>" + type + "</Type>\r\n");
+        }
+        recordInfoXml.append("</Query>\r\n");
+
+        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+
+        Request request = headerProvider.createMessageRequest(device, recordInfoXml.toString(),
+                SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
+
+        transmitRequest(device.getTransport(), request, errorEvent, okEvent);
+    }
+
+    /**
+     * 查询报警信息
+     *
+     * @param device        视频设备
+     * @param startPriority 报警起始级别(可选)
+     * @param endPriority   报警终止级别(可选)
+     * @param alarmMethod   报警方式条件(可选)
+     * @param alarmType     报警类型
+     * @param startTime     报警发生起始时间(可选)
+     * @param endTime       报警发生终止时间(可选)
+     * @return true = 命令发送成功
+     */
+    @Override
+    public void alarmInfoQuery(Device device, String startPriority, String endPriority, String alarmMethod, String alarmType,
+                               String startTime, String endTime, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer cmdXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        cmdXml.append("<Query>\r\n");
+        cmdXml.append("<CmdType>Alarm</CmdType>\r\n");
+        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        if (!ObjectUtils.isEmpty(startPriority)) {
+            cmdXml.append("<StartAlarmPriority>" + startPriority + "</StartAlarmPriority>\r\n");
+        }
+        if (!ObjectUtils.isEmpty(endPriority)) {
+            cmdXml.append("<EndAlarmPriority>" + endPriority + "</EndAlarmPriority>\r\n");
+        }
+        if (!ObjectUtils.isEmpty(alarmMethod)) {
+            cmdXml.append("<AlarmMethod>" + alarmMethod + "</AlarmMethod>\r\n");
+        }
+        if (!ObjectUtils.isEmpty(alarmType)) {
+            cmdXml.append("<AlarmType>" + alarmType + "</AlarmType>\r\n");
+        }
+        if (!ObjectUtils.isEmpty(startTime)) {
+            cmdXml.append("<StartAlarmTime>" + startTime + "</StartAlarmTime>\r\n");
+        }
+        if (!ObjectUtils.isEmpty(endTime)) {
+            cmdXml.append("<EndAlarmTime>" + endTime + "</EndAlarmTime>\r\n");
+        }
+        cmdXml.append("</Query>\r\n");
+
+        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
+        transmitRequest(device.getTransport(), request, errorEvent);
+    }
+
+    /**
+     * 查询设备配置
+     *
+     * @param device     视频设备
+     * @param channelId  通道编码(可选)
+     * @param configType 配置类型:
+     */
+    @Override
+    public void deviceConfigQuery(Device device, String channelId, String configType, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer cmdXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        cmdXml.append("<Query>\r\n");
+        cmdXml.append("<CmdType>ConfigDownload</CmdType>\r\n");
+        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        if (ObjectUtils.isEmpty(channelId)) {
+            cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        } else {
+            cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
+        }
+        cmdXml.append("<ConfigType>" + configType + "</ConfigType>\r\n");
+        cmdXml.append("</Query>\r\n");
+
+        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
+        transmitRequest(device.getTransport(), request, errorEvent);
+    }
+
+    /**
+     * 查询设备预置位置
+     *
+     * @param device 视频设备
+     */
+    @Override
+    public void presetQuery(Device device, String channelId, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer cmdXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        cmdXml.append("<Query>\r\n");
+        cmdXml.append("<CmdType>PresetQuery</CmdType>\r\n");
+        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        if (ObjectUtils.isEmpty(channelId)) {
+            cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        } else {
+            cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
+        }
+        cmdXml.append("</Query>\r\n");
+
+        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
+        transmitRequest(device.getTransport(), request, errorEvent);
+    }
+
+    /**
+     * 查询移动设备位置数据
+     *
+     * @param device 视频设备
+     */
+    @Override
+    public void mobilePostitionQuery(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer mobilePostitionXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        mobilePostitionXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        mobilePostitionXml.append("<Query>\r\n");
+        mobilePostitionXml.append("<CmdType>MobilePosition</CmdType>\r\n");
+        mobilePostitionXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        mobilePostitionXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        mobilePostitionXml.append("<Interval>60</Interval>\r\n");
+        mobilePostitionXml.append("</Query>\r\n");
+
+        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+
+        Request request = headerProvider.createMessageRequest(device, mobilePostitionXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
+
+        transmitRequest(device.getTransport(), request, errorEvent);
+
+    }
+
+    /**
+     * 订阅、取消订阅移动位置
+     *
+     * @param device 视频设备
+     * @return true = 命令发送成功
+     */
+    @Override
+    public SIPRequest mobilePositionSubscribe(Device device, SIPRequest requestOld, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer subscribePostitionXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        subscribePostitionXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        subscribePostitionXml.append("<Query>\r\n");
+        subscribePostitionXml.append("<CmdType>MobilePosition</CmdType>\r\n");
+        subscribePostitionXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        subscribePostitionXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        if (device.getSubscribeCycleForMobilePosition() > 0) {
+            subscribePostitionXml.append("<Interval>" + device.getMobilePositionSubmissionInterval() + "</Interval>\r\n");
+        }
+        subscribePostitionXml.append("</Query>\r\n");
+
+        CallIdHeader callIdHeader;
+
+        if (requestOld != null) {
+            callIdHeader = sipFactory.createHeaderFactory().createCallIdHeader(requestOld.getCallIdHeader().getCallId());
+        } else {
+            callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+                    : udpSipProvider.getNewCallId();
+        }
+        SIPRequest request = (SIPRequest) headerProvider.createSubscribeRequest(device, subscribePostitionXml.toString(), requestOld, device.getSubscribeCycleForMobilePosition(), "presence", callIdHeader); //Position;id=" + tm.substring(tm.length() - 4));
+
+        transmitRequest(device.getTransport(), request, errorEvent, okEvent);
+        return request;
+    }
+
+    /**
+     * 订阅、取消订阅报警信息
+     *
+     * @param device        视频设备
+     * @param expires       订阅过期时间(0 = 取消订阅)
+     * @param startPriority 报警起始级别(可选)
+     * @param endPriority   报警终止级别(可选)
+     * @param alarmMethod   报警方式条件(可选)
+     * @param alarmType     报警类型
+     * @param startTime     报警发生起始时间(可选)
+     * @param endTime       报警发生终止时间(可选)
+     * @return true = 命令发送成功
+     */
+    @Override
+    public void alarmSubscribe(Device device, int expires, String startPriority, String endPriority, String alarmMethod, String alarmType, String startTime, String endTime) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer cmdXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        cmdXml.append("<Query>\r\n");
+        cmdXml.append("<CmdType>Alarm</CmdType>\r\n");
+        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        if (!ObjectUtils.isEmpty(startPriority)) {
+            cmdXml.append("<StartAlarmPriority>" + startPriority + "</StartAlarmPriority>\r\n");
+        }
+        if (!ObjectUtils.isEmpty(endPriority)) {
+            cmdXml.append("<EndAlarmPriority>" + endPriority + "</EndAlarmPriority>\r\n");
+        }
+        if (!ObjectUtils.isEmpty(alarmMethod)) {
+            cmdXml.append("<AlarmMethod>" + alarmMethod + "</AlarmMethod>\r\n");
+        }
+        if (!ObjectUtils.isEmpty(alarmType)) {
+            cmdXml.append("<AlarmType>" + alarmType + "</AlarmType>\r\n");
+        }
+        if (!ObjectUtils.isEmpty(startTime)) {
+            cmdXml.append("<StartAlarmTime>" + startTime + "</StartAlarmTime>\r\n");
+        }
+        if (!ObjectUtils.isEmpty(endTime)) {
+            cmdXml.append("<EndAlarmTime>" + endTime + "</EndAlarmTime>\r\n");
+        }
+        cmdXml.append("</Query>\r\n");
+
+        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+
+        Request request = headerProvider.createSubscribeRequest(device, cmdXml.toString(), null, expires, "presence", callIdHeader);
+        transmitRequest(device.getTransport(), request);
+
+    }
+
+    @Override
+    public SIPRequest catalogSubscribe(Device device, SIPRequest requestOld, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer cmdXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        cmdXml.append("<Query>\r\n");
+        cmdXml.append("<CmdType>Catalog</CmdType>\r\n");
+        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        cmdXml.append("</Query>\r\n");
+
+        CallIdHeader callIdHeader;
+
+        if (requestOld != null) {
+            callIdHeader = sipFactory.createHeaderFactory().createCallIdHeader(requestOld.getCallIdHeader().getCallId());
+        } else {
+            callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+                    : udpSipProvider.getNewCallId();
+        }
+
+        // 有效时间默认为60秒以上
+        SIPRequest request = (SIPRequest) headerProvider.createSubscribeRequest(device, cmdXml.toString(), requestOld, device.getSubscribeCycleForCatalog(), "Catalog",
+                callIdHeader);
+        transmitRequest(device.getTransport(), request, errorEvent, okEvent);
+        return request;
+    }
+
+    @Override
+    public void dragZoomCmd(Device device, String channelId, String cmdString) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer dragXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        dragXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        dragXml.append("<Control>\r\n");
+        dragXml.append("<CmdType>DeviceControl</CmdType>\r\n");
+        dragXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        if (ObjectUtils.isEmpty(channelId)) {
+            dragXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        } else {
+            dragXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
+        }
+        dragXml.append(cmdString);
+        dragXml.append("</Control>\r\n");
+        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+        Request request = headerProvider.createMessageRequest(device, dragXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
+        logger.debug("拉框信令: " + request.toString());
+        transmitRequest(device.getTransport(), request);
+    }
+
+
+    @Override
+    public void transmitRequest(String transport, Request request) throws SipException, ParseException {
+        transmitRequest(transport, request, null, null);
+    }
+
+    @Override
+    public void transmitRequest(String transport, Request request, SipSubscribe.Event errorEvent) throws SipException, ParseException {
+        transmitRequest(transport, request, errorEvent, null);
+    }
+
+    @Override
+    public void transmitRequest(String transport, Request request, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, ParseException {
+
+        if (request.getHeader(UserAgentHeader.NAME) == null) {
+            try {
+                request.addHeader(SipUtils.createUserAgentHeader(sipFactory, gitUtil));
+            } catch (ParseException e) {
+                logger.error("添加UserAgentHeader失败", e);
+            }
+        }
+
+        CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME);
+        // 添加错误订阅
+        if (errorEvent != null) {
+            sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), (eventResult -> {
+                errorEvent.response(eventResult);
+                sipSubscribe.removeErrorSubscribe(eventResult.callId);
+                sipSubscribe.removeOkSubscribe(eventResult.callId);
+            }));
+        }
+        // 添加订阅
+        if (okEvent != null) {
+            sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), eventResult -> {
+                okEvent.response(eventResult);
+                sipSubscribe.removeOkSubscribe(eventResult.callId);
+                sipSubscribe.removeErrorSubscribe(eventResult.callId);
+            });
+        }
+        if ("TCP".equals(transport)) {
+            tcpSipProvider.sendRequest(request);
+        } else if ("UDP".equals(transport)) {
+            udpSipProvider.sendRequest(request);
+        }
+
+    }
+
+
+    /**
+     * 回放暂停
+     */
+    @Override
+    public void playPauseCmd(Device device, StreamInfo streamInfo) throws InvalidArgumentException, ParseException, SipException {
+        StringBuffer content = new StringBuffer(200);
+        content.append("PAUSE RTSP/1.0\r\n");
+        content.append("CSeq: " + getInfoCseq() + "\r\n");
+        content.append("PauseTime: now\r\n");
+
+        playbackControlCmd(device, streamInfo, content.toString(), null, null);
+    }
+
+
+    /**
+     * 回放恢复
+     */
+    @Override
+    public void playResumeCmd(Device device, StreamInfo streamInfo) throws InvalidArgumentException, ParseException, SipException {
+        StringBuffer content = new StringBuffer(200);
+        content.append("PLAY RTSP/1.0\r\n");
+        content.append("CSeq: " + getInfoCseq() + "\r\n");
+        content.append("Range: npt=now-\r\n");
+
+        playbackControlCmd(device, streamInfo, content.toString(), null, null);
+    }
+
+    /**
+     * 回放拖动播放
+     */
+    @Override
+    public void playSeekCmd(Device device, StreamInfo streamInfo, long seekTime) throws InvalidArgumentException, ParseException, SipException {
+        StringBuffer content = new StringBuffer(200);
+        content.append("PLAY RTSP/1.0\r\n");
+        content.append("CSeq: " + getInfoCseq() + "\r\n");
+        content.append("Range: npt=" + Math.abs(seekTime) + "-\r\n");
+
+        playbackControlCmd(device, streamInfo, content.toString(), null, null);
+    }
+
+    /**
+     * 回放倍速播放
+     */
+    @Override
+    public void playSpeedCmd(Device device, StreamInfo streamInfo, Double speed) throws InvalidArgumentException, ParseException, SipException {
+        StringBuffer content = new StringBuffer(200);
+        content.append("PLAY RTSP/1.0\r\n");
+        content.append("CSeq: " + getInfoCseq() + "\r\n");
+        content.append("Scale: " + String.format("%.6f", speed) + "\r\n");
+
+        playbackControlCmd(device, streamInfo, content.toString(), null, null);
+    }
+
+    private int getInfoCseq() {
+        return (int) ((Math.random() * 9 + 1) * Math.pow(10, 8));
+    }
+
+    @Override
+    public void playbackControlCmd(Device device, StreamInfo streamInfo, String content, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException {
+
+        SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(device.getDeviceId(), streamInfo.getChannelId(), null, streamInfo.getStream());
+        if (ssrcTransaction == null) {
+            logger.info("[回放控制]未找到视频流信息,设备:{}, 流ID: {}", device.getDeviceId(), streamInfo.getStream());
+            return;
+        }
+
+        SIPRequest request = headerProvider.createInfoRequest(device, streamInfo.getChannelId(), content.toString(), ssrcTransaction.getSipTransactionInfo());
+        if (request == null) {
+            logger.info("[回放控制]构建Request信息失败,设备:{}, 流ID: {}", device.getDeviceId(), streamInfo.getStream());
+            return;
+        }
+
+        transmitRequest(device.getTransport(), request, errorEvent, okEvent);
+    }
+
+    @Override
+    public void sendAlarmMessage(Device device, DeviceAlarm deviceAlarm) throws InvalidArgumentException, SipException, ParseException {
+        if (device == null) {
+            return;
+        }
+        logger.info("[发送 报警通知] {}/{}->{},{}", device.getDeviceId(), deviceAlarm.getChannelId(),
+                deviceAlarm.getLongitude(), deviceAlarm.getLatitude());
+
+        String characterSet = device.getCharset();
+        StringBuffer deviceStatusXml = new StringBuffer(600);
+        deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
+        deviceStatusXml.append("<Notify>\r\n");
+        deviceStatusXml.append("<CmdType>Alarm</CmdType>\r\n");
+        deviceStatusXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        deviceStatusXml.append("<DeviceID>" + deviceAlarm.getChannelId() + "</DeviceID>\r\n");
+        deviceStatusXml.append("<AlarmPriority>" + deviceAlarm.getAlarmPriority() + "</AlarmPriority>\r\n");
+        deviceStatusXml.append("<AlarmMethod>" + deviceAlarm.getAlarmMethod() + "</AlarmMethod>\r\n");
+        deviceStatusXml.append("<AlarmTime>" + deviceAlarm.getAlarmTime() + "</AlarmTime>\r\n");
+        deviceStatusXml.append("<AlarmDescription>" + deviceAlarm.getAlarmDescription() + "</AlarmDescription>\r\n");
+        deviceStatusXml.append("<Longitude>" + deviceAlarm.getLongitude() + "</Longitude>\r\n");
+        deviceStatusXml.append("<Latitude>" + deviceAlarm.getLatitude() + "</Latitude>\r\n");
+        deviceStatusXml.append("<info>\r\n");
+        deviceStatusXml.append("<AlarmType>" + deviceAlarm.getAlarmType() + "</AlarmType>\r\n");
+        deviceStatusXml.append("</info>\r\n");
+        deviceStatusXml.append("</Notify>\r\n");
+
+        CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+        Request request = headerProvider.createMessageRequest(device, deviceStatusXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
+        transmitRequest(device.getTransport(), request);
+
+
+    }
 }

+ 226 - 390
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java

@@ -13,12 +13,13 @@ import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
+import com.genersoft.iot.vmp.utils.GitUtil;
 import com.genersoft.iot.vmp.utils.SerializeUtils;
+import gov.nist.javax.sip.SIPConstants;
 import gov.nist.javax.sip.SipProviderImpl;
 import gov.nist.javax.sip.SipStackImpl;
 import gov.nist.javax.sip.message.MessageFactoryImpl;
 import gov.nist.javax.sip.message.SIPRequest;
-import gov.nist.javax.sip.stack.SIPDialog;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -26,17 +27,15 @@ import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.context.annotation.DependsOn;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.lang.Nullable;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
 
 import javax.sip.*;
-import javax.sip.address.SipURI;
 import javax.sip.header.*;
 import javax.sip.message.Request;
-import java.lang.reflect.Field;
 import java.text.ParseException;
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.List;
 
 @Component
@@ -77,19 +76,18 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
     private SubscribeHolder subscribeHolder;
 
     @Override
-    public boolean register(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) {
-        return register(parentPlatform, null, null, errorEvent, okEvent, false, true);
+    public void register(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException {
+        register(parentPlatform, null, null, errorEvent, okEvent, false, true);
     }
 
     @Override
-    public boolean unregister(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) {
-        return register(parentPlatform, null, null, errorEvent, okEvent, false, false);
+    public void unregister(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException {
+        register(parentPlatform, null, null, errorEvent, okEvent, false, false);
     }
 
     @Override
-    public boolean register(ParentPlatform parentPlatform, @Nullable String callId, @Nullable WWWAuthenticateHeader www,
-                            SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent, boolean registerAgain, boolean isRegister) {
-        try {
+    public void register(ParentPlatform parentPlatform, @Nullable String callId, @Nullable WWWAuthenticateHeader www,
+                            SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent, boolean registerAgain, boolean isRegister) throws SipException, InvalidArgumentException, ParseException {
             Request request;
             if (!registerAgain ) {
                 CallIdHeader callIdHeader = null;
@@ -126,23 +124,10 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
             }
 
             transmitRequest(parentPlatform, request, null, okEvent);
-            return true;
-        } catch (ParseException e) {
-            e.printStackTrace();
-        } catch (InvalidArgumentException e) {
-            e.printStackTrace();
-        } catch (PeerUnavailableException e) {
-            e.printStackTrace();
-        } catch (SipException e) {
-            e.printStackTrace();
-        }
-        return false;
     }
 
     @Override
-    public String keepalive(ParentPlatform parentPlatform,SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) {
-        String callId = null;
-        try {
+    public String keepalive(ParentPlatform parentPlatform,SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException {
             String characterSet = parentPlatform.getCharacterSet();
             StringBuffer keepaliveXml = new StringBuffer(200);
             keepaliveXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
@@ -163,11 +148,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
                     SipUtils.getNewViaTag(),
                     callIdHeader);
             transmitRequest(parentPlatform, request, errorEvent, okEvent);
-            callId = callIdHeader.getCallId();
-        } catch (ParseException | InvalidArgumentException | SipException e) {
-            e.printStackTrace();
-        }
-        return callId;
+        return callIdHeader.getCallId();
     }
 
     private void transmitRequest(ParentPlatform parentPlatform, Request request) throws SipException {
@@ -206,39 +187,32 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
      * @return
      */
     @Override
-    public boolean catalogQuery(DeviceChannel channel, ParentPlatform parentPlatform, String sn, String fromTag, int size) {
+    public void catalogQuery(DeviceChannel channel, ParentPlatform parentPlatform, String sn, String fromTag, int size) throws SipException, InvalidArgumentException, ParseException {
 
         if ( parentPlatform ==null) {
-            return false;
+            return ;
         }
-        try {
-            List<DeviceChannel> channels = new ArrayList<>();
-            if (channel != null) {
-                channels.add(channel);
-            }
-            String catalogXml = getCatalogXml(channels, sn, parentPlatform, size);
+        List<DeviceChannel> channels = new ArrayList<>();
+        if (channel != null) {
+            channels.add(channel);
+        }
+        String catalogXml = getCatalogXml(channels, sn, parentPlatform, size);
 
-            // callid
-            CallIdHeader callIdHeader = parentPlatform.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
-                    : udpSipProvider.getNewCallId();
+        // callid
+        CallIdHeader callIdHeader = parentPlatform.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
 
-            Request request = headerProviderPlatformProvider.createMessageRequest(parentPlatform, catalogXml.toString(), fromTag, SipUtils.getNewViaTag(), callIdHeader);
-            transmitRequest(parentPlatform, request);
+        Request request = headerProviderPlatformProvider.createMessageRequest(parentPlatform, catalogXml.toString(), fromTag, SipUtils.getNewViaTag(), callIdHeader);
+        transmitRequest(parentPlatform, request);
 
-        } catch (SipException | ParseException | InvalidArgumentException e) {
-            e.printStackTrace();
-            return false;
-        }
-        return true;
     }
 
     @Override
-    public boolean catalogQuery(List<DeviceChannel> channels, ParentPlatform parentPlatform, String sn, String fromTag) {
+    public void catalogQuery(List<DeviceChannel> channels, ParentPlatform parentPlatform, String sn, String fromTag) throws InvalidArgumentException, ParseException, SipException {
         if ( parentPlatform ==null) {
-            return false;
+            return ;
         }
         sendCatalogResponse(channels, parentPlatform, sn, fromTag, 0);
-        return true;
     }
     private String getCatalogXml(List<DeviceChannel> channels, String sn, ParentPlatform parentPlatform, int size) {
         String characterSet = parentPlatform.getCharacterSet();
@@ -300,30 +274,30 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         return catalogXml.toString();
     }
 
-    private void sendCatalogResponse(List<DeviceChannel> channels, ParentPlatform parentPlatform, String sn, String fromTag, int index) {
+    private void sendCatalogResponse(List<DeviceChannel> channels, ParentPlatform parentPlatform, String sn, String fromTag, int index) throws SipException, InvalidArgumentException, ParseException {
         if (index >= channels.size()) {
             return;
         }
-        try {
-            List<DeviceChannel> deviceChannels;
-            if (index + parentPlatform.getCatalogGroup() < channels.size()) {
-                deviceChannels = channels.subList(index, index + parentPlatform.getCatalogGroup());
-            }else {
-                deviceChannels = channels.subList(index, channels.size());
-            }
-            String catalogXml = getCatalogXml(deviceChannels, sn, parentPlatform, channels.size());
-            // callid
-            CallIdHeader callIdHeader = parentPlatform.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
-                    : udpSipProvider.getNewCallId();
+        List<DeviceChannel> deviceChannels;
+        if (index + parentPlatform.getCatalogGroup() < channels.size()) {
+            deviceChannels = channels.subList(index, index + parentPlatform.getCatalogGroup());
+        }else {
+            deviceChannels = channels.subList(index, channels.size());
+        }
+        String catalogXml = getCatalogXml(deviceChannels, sn, parentPlatform, channels.size());
+        // callid
+        CallIdHeader callIdHeader = parentPlatform.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
 
-            Request request = headerProviderPlatformProvider.createMessageRequest(parentPlatform, catalogXml, fromTag, SipUtils.getNewViaTag(), callIdHeader);
-            transmitRequest(parentPlatform, request, null, eventResult -> {
-                int indexNext = index + parentPlatform.getCatalogGroup();
+        Request request = headerProviderPlatformProvider.createMessageRequest(parentPlatform, catalogXml, fromTag, SipUtils.getNewViaTag(), callIdHeader);
+        transmitRequest(parentPlatform, request, null, eventResult -> {
+            int indexNext = index + parentPlatform.getCatalogGroup();
+            try {
                 sendCatalogResponse(channels, parentPlatform, sn, fromTag, indexNext);
-            });
-        } catch (SipException | ParseException | InvalidArgumentException e) {
-            e.printStackTrace();
-        }
+            } catch (SipException | InvalidArgumentException | ParseException e) {
+                logger.error("[命令发送失败] 国标级联 目录查询回复: {}", e.getMessage());
+            }
+        });
     }
 
     /**
@@ -334,36 +308,29 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
      * @return
      */
     @Override
-    public boolean deviceInfoResponse(ParentPlatform parentPlatform, String sn, String fromTag) {
+    public void deviceInfoResponse(ParentPlatform parentPlatform, String sn, String fromTag) throws SipException, InvalidArgumentException, ParseException {
         if (parentPlatform == null) {
-            return false;
-        }
-        try {
-            String characterSet = parentPlatform.getCharacterSet();
-            StringBuffer deviceInfoXml = new StringBuffer(600);
-            deviceInfoXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-            deviceInfoXml.append("<Response>\r\n");
-            deviceInfoXml.append("<CmdType>DeviceInfo</CmdType>\r\n");
-            deviceInfoXml.append("<SN>" +sn + "</SN>\r\n");
-            deviceInfoXml.append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n");
-            deviceInfoXml.append("<DeviceName>" + parentPlatform.getName() + "</DeviceName>\r\n");
-            deviceInfoXml.append("<Manufacturer>wvp</Manufacturer>\r\n");
-            deviceInfoXml.append("<Model>wvp-28181-2.0</Model>\r\n");
-            deviceInfoXml.append("<Firmware>2.0.202107</Firmware>\r\n");
-            deviceInfoXml.append("<Result>OK</Result>\r\n");
-            deviceInfoXml.append("</Response>\r\n");
-
-            CallIdHeader callIdHeader = parentPlatform.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
-                    : udpSipProvider.getNewCallId();
-
-            Request request = headerProviderPlatformProvider.createMessageRequest(parentPlatform, deviceInfoXml.toString(), fromTag, SipUtils.getNewViaTag(), callIdHeader);
-            transmitRequest(parentPlatform, request);
-
-        } catch (SipException | ParseException | InvalidArgumentException e) {
-            e.printStackTrace();
-            return false;
+            return;
         }
-        return true;
+        String characterSet = parentPlatform.getCharacterSet();
+        StringBuffer deviceInfoXml = new StringBuffer(600);
+        deviceInfoXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
+        deviceInfoXml.append("<Response>\r\n");
+        deviceInfoXml.append("<CmdType>DeviceInfo</CmdType>\r\n");
+        deviceInfoXml.append("<SN>" +sn + "</SN>\r\n");
+        deviceInfoXml.append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n");
+        deviceInfoXml.append("<DeviceName>" + parentPlatform.getName() + "</DeviceName>\r\n");
+        deviceInfoXml.append("<Manufacturer>wvp</Manufacturer>\r\n");
+        deviceInfoXml.append("<Model>wvp-28181-2.0</Model>\r\n");
+        deviceInfoXml.append("<Firmware>2.0.202107</Firmware>\r\n");
+        deviceInfoXml.append("<Result>OK</Result>\r\n");
+        deviceInfoXml.append("</Response>\r\n");
+
+        CallIdHeader callIdHeader = parentPlatform.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+
+        Request request = headerProviderPlatformProvider.createMessageRequest(parentPlatform, deviceInfoXml.toString(), fromTag, SipUtils.getNewViaTag(), callIdHeader);
+        transmitRequest(parentPlatform, request);
     }
 
     /**
@@ -374,129 +341,103 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
      * @return
      */
     @Override
-    public boolean deviceStatusResponse(ParentPlatform parentPlatform, String sn, String fromTag) {
+    public void deviceStatusResponse(ParentPlatform parentPlatform, String sn, String fromTag) throws SipException, InvalidArgumentException, ParseException {
         if (parentPlatform == null) {
-            return false;
+            return ;
         }
-        try {
-            String characterSet = parentPlatform.getCharacterSet();
-            StringBuffer deviceStatusXml = new StringBuffer(600);
-            deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-            deviceStatusXml.append("<Response>\r\n");
-            deviceStatusXml.append("<CmdType>DeviceStatus</CmdType>\r\n");
-            deviceStatusXml.append("<SN>" +sn + "</SN>\r\n");
-            deviceStatusXml.append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n");
-            deviceStatusXml.append("<Result>OK</Result>\r\n");
-            deviceStatusXml.append("<Online>ONLINE</Online>\r\n");
-            deviceStatusXml.append("<Status>OK</Status>\r\n");
-            deviceStatusXml.append("</Response>\r\n");
-
-            CallIdHeader callIdHeader = parentPlatform.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
-                    : udpSipProvider.getNewCallId();
-
-            Request request = headerProviderPlatformProvider.createMessageRequest(parentPlatform, deviceStatusXml.toString(), fromTag, SipUtils.getNewViaTag(), callIdHeader);
-            transmitRequest(parentPlatform, request);
+        String characterSet = parentPlatform.getCharacterSet();
+        StringBuffer deviceStatusXml = new StringBuffer(600);
+        deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
+        deviceStatusXml.append("<Response>\r\n");
+        deviceStatusXml.append("<CmdType>DeviceStatus</CmdType>\r\n");
+        deviceStatusXml.append("<SN>" +sn + "</SN>\r\n");
+        deviceStatusXml.append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n");
+        deviceStatusXml.append("<Result>OK</Result>\r\n");
+        deviceStatusXml.append("<Online>ONLINE</Online>\r\n");
+        deviceStatusXml.append("<Status>OK</Status>\r\n");
+        deviceStatusXml.append("</Response>\r\n");
+
+        CallIdHeader callIdHeader = parentPlatform.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+
+        Request request = headerProviderPlatformProvider.createMessageRequest(parentPlatform, deviceStatusXml.toString(), fromTag, SipUtils.getNewViaTag(), callIdHeader);
+        transmitRequest(parentPlatform, request);
 
-        } catch (SipException | ParseException | InvalidArgumentException e) {
-            e.printStackTrace();
-            return false;
-        }
-        return true;
     }
 
     @Override
-    public boolean sendNotifyMobilePosition(ParentPlatform parentPlatform, GPSMsgInfo gpsMsgInfo, SubscribeInfo subscribeInfo) {
+    public void sendNotifyMobilePosition(ParentPlatform parentPlatform, GPSMsgInfo gpsMsgInfo, SubscribeInfo subscribeInfo) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException {
         if (parentPlatform == null) {
-            return false;
+            return;
         }
         if (logger.isDebugEnabled()) {
             logger.debug("[发送 移动位置订阅] {}/{}->{},{}", parentPlatform.getServerGBId(), gpsMsgInfo.getId(), gpsMsgInfo.getLng(), gpsMsgInfo.getLat());
         }
 
-        try {
-            String characterSet = parentPlatform.getCharacterSet();
-            StringBuffer deviceStatusXml = new StringBuffer(600);
-            deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-            deviceStatusXml.append("<Notify>\r\n");
-            deviceStatusXml.append("<CmdType>MobilePosition</CmdType>\r\n");
-            deviceStatusXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-            deviceStatusXml.append("<DeviceID>" + gpsMsgInfo.getId() + "</DeviceID>\r\n");
-            deviceStatusXml.append("<Time>" + gpsMsgInfo.getTime() + "</Time>\r\n");
-            deviceStatusXml.append("<Longitude>" + gpsMsgInfo.getLng() + "</Longitude>\r\n");
-            deviceStatusXml.append("<Latitude>" + gpsMsgInfo.getLat() + "</Latitude>\r\n");
-            deviceStatusXml.append("<Speed>" + gpsMsgInfo.getSpeed() + "</Speed>\r\n");
-            deviceStatusXml.append("<Direction>" + gpsMsgInfo.getDirection() + "</Direction>\r\n");
-            deviceStatusXml.append("<Altitude>" + gpsMsgInfo.getAltitude() + "</Altitude>\r\n");
-            deviceStatusXml.append("</Notify>\r\n");
-
-           sendNotify(parentPlatform, deviceStatusXml.toString(), subscribeInfo, eventResult -> {
-                logger.error("发送NOTIFY通知消息失败。错误:{} {}", eventResult.statusCode, eventResult.msg);
-            }, null);
-
-        } catch (SipException | ParseException  e) {
-            e.printStackTrace();
-            return false;
-        } catch (NoSuchFieldException e) {
-            e.printStackTrace();
-        } catch (IllegalAccessException e) {
-            e.printStackTrace();
-        } catch (InvalidArgumentException e) {
-            e.printStackTrace();
-        }
-        return true;
+        String characterSet = parentPlatform.getCharacterSet();
+        StringBuffer deviceStatusXml = new StringBuffer(600);
+        deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
+        deviceStatusXml.append("<Notify>\r\n");
+        deviceStatusXml.append("<CmdType>MobilePosition</CmdType>\r\n");
+        deviceStatusXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
+        deviceStatusXml.append("<DeviceID>" + gpsMsgInfo.getId() + "</DeviceID>\r\n");
+        deviceStatusXml.append("<Time>" + gpsMsgInfo.getTime() + "</Time>\r\n");
+        deviceStatusXml.append("<Longitude>" + gpsMsgInfo.getLng() + "</Longitude>\r\n");
+        deviceStatusXml.append("<Latitude>" + gpsMsgInfo.getLat() + "</Latitude>\r\n");
+        deviceStatusXml.append("<Speed>" + gpsMsgInfo.getSpeed() + "</Speed>\r\n");
+        deviceStatusXml.append("<Direction>" + gpsMsgInfo.getDirection() + "</Direction>\r\n");
+        deviceStatusXml.append("<Altitude>" + gpsMsgInfo.getAltitude() + "</Altitude>\r\n");
+        deviceStatusXml.append("</Notify>\r\n");
+
+       sendNotify(parentPlatform, deviceStatusXml.toString(), subscribeInfo, eventResult -> {
+            logger.error("发送NOTIFY通知消息失败。错误:{} {}", eventResult.statusCode, eventResult.msg);
+        }, null);
+
     }
 
     @Override
-    public boolean sendAlarmMessage(ParentPlatform parentPlatform, DeviceAlarm deviceAlarm) {
+    public void sendAlarmMessage(ParentPlatform parentPlatform, DeviceAlarm deviceAlarm) throws SipException, InvalidArgumentException, ParseException {
         if (parentPlatform == null) {
-            return false;
+            return;
         }
         logger.info("[发送报警通知] {}/{}->{},{}: {}", parentPlatform.getServerGBId(), deviceAlarm.getChannelId(),
                 deviceAlarm.getLongitude(), deviceAlarm.getLatitude(), JSONObject.toJSON(deviceAlarm));
-        try {
-            String characterSet = parentPlatform.getCharacterSet();
-            StringBuffer deviceStatusXml = new StringBuffer(600);
-            deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-            deviceStatusXml.append("<Notify>\r\n");
-            deviceStatusXml.append("<CmdType>Alarm</CmdType>\r\n");
-            deviceStatusXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-            deviceStatusXml.append("<DeviceID>" + deviceAlarm.getChannelId() + "</DeviceID>\r\n");
-            deviceStatusXml.append("<AlarmPriority>" + deviceAlarm.getAlarmPriority() + "</AlarmPriority>\r\n");
-            deviceStatusXml.append("<AlarmMethod>" + deviceAlarm.getAlarmMethod() + "</AlarmMethod>\r\n");
-            deviceStatusXml.append("<AlarmTime>" + deviceAlarm.getAlarmTime() + "</AlarmTime>\r\n");
-            deviceStatusXml.append("<AlarmDescription>" + deviceAlarm.getAlarmDescription() + "</AlarmDescription>\r\n");
-            deviceStatusXml.append("<Longitude>" + deviceAlarm.getLongitude() + "</Longitude>\r\n");
-            deviceStatusXml.append("<Latitude>" + deviceAlarm.getLatitude() + "</Latitude>\r\n");
-            deviceStatusXml.append("<info>\r\n");
-            deviceStatusXml.append("<AlarmType>" + deviceAlarm.getAlarmType() + "</AlarmType>\r\n");
-            deviceStatusXml.append("</info>\r\n");
-            deviceStatusXml.append("</Notify>\r\n");
-
-            CallIdHeader callIdHeader = parentPlatform.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
-                    : udpSipProvider.getNewCallId();
-
-            Request request = headerProviderPlatformProvider.createMessageRequest(parentPlatform, deviceStatusXml.toString(), SipUtils.getNewFromTag(), SipUtils.getNewViaTag(), callIdHeader);
-            transmitRequest(parentPlatform, request);
+        String characterSet = parentPlatform.getCharacterSet();
+        StringBuffer deviceStatusXml = new StringBuffer(600);
+        deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
+        deviceStatusXml.append("<Notify>\r\n");
+        deviceStatusXml.append("<CmdType>Alarm</CmdType>\r\n");
+        deviceStatusXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
+        deviceStatusXml.append("<DeviceID>" + deviceAlarm.getChannelId() + "</DeviceID>\r\n");
+        deviceStatusXml.append("<AlarmPriority>" + deviceAlarm.getAlarmPriority() + "</AlarmPriority>\r\n");
+        deviceStatusXml.append("<AlarmMethod>" + deviceAlarm.getAlarmMethod() + "</AlarmMethod>\r\n");
+        deviceStatusXml.append("<AlarmTime>" + deviceAlarm.getAlarmTime() + "</AlarmTime>\r\n");
+        deviceStatusXml.append("<AlarmDescription>" + deviceAlarm.getAlarmDescription() + "</AlarmDescription>\r\n");
+        deviceStatusXml.append("<Longitude>" + deviceAlarm.getLongitude() + "</Longitude>\r\n");
+        deviceStatusXml.append("<Latitude>" + deviceAlarm.getLatitude() + "</Latitude>\r\n");
+        deviceStatusXml.append("<info>\r\n");
+        deviceStatusXml.append("<AlarmType>" + deviceAlarm.getAlarmType() + "</AlarmType>\r\n");
+        deviceStatusXml.append("</info>\r\n");
+        deviceStatusXml.append("</Notify>\r\n");
+
+        CallIdHeader callIdHeader = parentPlatform.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+
+        Request request = headerProviderPlatformProvider.createMessageRequest(parentPlatform, deviceStatusXml.toString(), SipUtils.getNewFromTag(), SipUtils.getNewViaTag(), callIdHeader);
+        transmitRequest(parentPlatform, request);
 
-        } catch (SipException | ParseException  e) {
-            e.printStackTrace();
-            return false;
-        } catch (InvalidArgumentException e) {
-            e.printStackTrace();
-        }
-        return true;
     }
 
     @Override
-    public boolean sendNotifyForCatalogAddOrUpdate(String type, ParentPlatform parentPlatform, List<DeviceChannel> deviceChannels, SubscribeInfo subscribeInfo, Integer index) {
+    public void sendNotifyForCatalogAddOrUpdate(String type, ParentPlatform parentPlatform, List<DeviceChannel> deviceChannels, SubscribeInfo subscribeInfo, Integer index) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException {
         if (parentPlatform == null || deviceChannels == null || deviceChannels.size() == 0 || subscribeInfo == null) {
-            return false;
+            return;
         }
         if (index == null) {
             index = 0;
         }
         if (index >= deviceChannels.size()) {
-            return true;
+            return;
         }
         List<DeviceChannel> channels;
         if (index + parentPlatform.getCatalogGroup() < deviceChannels.size()) {
@@ -504,32 +445,25 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         }else {
             channels = deviceChannels.subList(index, deviceChannels.size());
         }
-        try {
-            Integer finalIndex = index;
-            String catalogXmlContent = getCatalogXmlContentForCatalogAddOrUpdate(parentPlatform, channels,
-                    deviceChannels.size(), type, subscribeInfo);
-            sendNotify(parentPlatform, catalogXmlContent, subscribeInfo, eventResult -> {
-                logger.error("发送NOTIFY通知消息失败。错误:{} {}", eventResult.statusCode, eventResult.msg);
-            }, (eventResult -> {
+        Integer finalIndex = index;
+        String catalogXmlContent = getCatalogXmlContentForCatalogAddOrUpdate(parentPlatform, channels,
+                deviceChannels.size(), type, subscribeInfo);
+        sendNotify(parentPlatform, catalogXmlContent, subscribeInfo, eventResult -> {
+            logger.error("发送NOTIFY通知消息失败。错误:{} {}", eventResult.statusCode, eventResult.msg);
+        }, (eventResult -> {
+            try {
                 sendNotifyForCatalogAddOrUpdate(type, parentPlatform, deviceChannels, subscribeInfo,
                         finalIndex + parentPlatform.getCatalogGroup());
-            }));
-        } catch (SipException | ParseException e) {
-            e.printStackTrace();
-            return false;
-        } catch (NoSuchFieldException e) {
-            e.printStackTrace();
-        } catch (IllegalAccessException e) {
-            e.printStackTrace();
-        } catch (InvalidArgumentException e) {
-            e.printStackTrace();
-        }
-        return true;
+            } catch (InvalidArgumentException | ParseException | NoSuchFieldException | SipException |
+                     IllegalAccessException e) {
+                logger.error("[命令发送失败] 国标级联 NOTIFY通知: {}", e.getMessage());
+            }
+        }));
     }
 
-    private ClientTransaction sendNotify(ParentPlatform parentPlatform, String catalogXmlContent,
+    private void sendNotify(ParentPlatform parentPlatform, String catalogXmlContent,
                                    SubscribeInfo subscribeInfo, SipSubscribe.Event errorEvent,  SipSubscribe.Event okEvent )
-            throws NoSuchFieldException, IllegalAccessException, SipException, ParseException, InvalidArgumentException {
+            throws SipException, ParseException, InvalidArgumentException {
 		MessageFactoryImpl messageFactory = (MessageFactoryImpl) sipFactory.createMessageFactory();
         String characterSet = parentPlatform.getCharacterSet();
  		// 设置编码, 防止中文乱码
@@ -537,50 +471,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
 
         SIPRequest notifyRequest = headerProviderPlatformProvider.createNotifyRequest(parentPlatform, catalogXmlContent, subscribeInfo);
 
-        notifyRequest.getCSeqHeader().setSeqNumber(redisCatchStorage.getCSEQ());
-        
-        ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
-        notifyRequest.setContent(catalogXmlContent, contentTypeHeader);
-
-        SubscriptionStateHeader subscriptionState = sipFactory.createHeaderFactory()
-                .createSubscriptionStateHeader(SubscriptionStateHeader.ACTIVE);
-        notifyRequest.addHeader(subscriptionState);
-
-        EventHeader event = sipFactory.createHeaderFactory().createEventHeader(subscribeInfo.getEventType());
-        if (subscribeInfo.getEventId() != null) {
-            event.setEventId(subscribeInfo.getEventId());
-        }
-        notifyRequest.addHeader(event);
-        SipURI sipURI = (SipURI) notifyRequest.getRequestURI();
-        sipURI.setHost(parentPlatform.getServerIP());
-        sipURI.setPort(parentPlatform.getServerPort());
-
-//        ClientTransaction transaction = subscribeInfo.getClientTransaction();
-//        if (transaction == null || transaction.getState().equals(TransactionState.COMPLETED)) {
-//            if ("TCP".equals(parentPlatform.getTransport())) {
-//                transaction = tcpSipProvider.getNewClientTransaction(notifyRequest);
-//            } else if ("UDP".equals(parentPlatform.getTransport())) {
-//                transaction = udpSipProvider.getNewClientTransaction(notifyRequest);
-//            }
-//        }
-
-        ClientTransaction transaction = null;
-        if ("TCP".equals(parentPlatform.getTransport())) {
-            transaction = tcpSipProvider.getNewClientTransaction(notifyRequest);
-        } else if ("UDP".equals(parentPlatform.getTransport())) {
-            transaction = udpSipProvider.getNewClientTransaction(notifyRequest);
-        }
-
-        // 添加错误订阅
-        if (errorEvent != null) {
-            sipSubscribe.addErrorSubscribe(subscribeInfo.getRequest().getCallIdHeader().getCallId(), errorEvent);
-        }
-        // 添加订阅
-        if (okEvent != null) {
-            sipSubscribe.addOkSubscribe(subscribeInfo.getRequest().getCallIdHeader().getCallId(), okEvent);
-        }
-        transaction.sendRequest();
-        return transaction;
+        transmitRequest(parentPlatform, notifyRequest);
     }
 
     private  String getCatalogXmlContentForCatalogAddOrUpdate(ParentPlatform parentPlatform, List<DeviceChannel> channels, int sumNum, String type, SubscribeInfo subscribeInfo) {
@@ -639,20 +530,21 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
     }
 
     @Override
-    public boolean sendNotifyForCatalogOther(String type, ParentPlatform parentPlatform, List<DeviceChannel> deviceChannels,
-                                             SubscribeInfo subscribeInfo, Integer index) {
+    public void sendNotifyForCatalogOther(String type, ParentPlatform parentPlatform, List<DeviceChannel> deviceChannels,
+                                             SubscribeInfo subscribeInfo, Integer index) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException {
         if (parentPlatform == null
                 || deviceChannels == null
                 || deviceChannels.size() == 0
                 || subscribeInfo == null) {
-            return false;
+            logger.warn("[缺少必要参数]");
+            return;
         }
 
         if (index == null) {
             index = 0;
         }
         if (index >= deviceChannels.size()) {
-            return true;
+            return;
         }
         List<DeviceChannel> channels;
         if (index + parentPlatform.getCatalogGroup() < deviceChannels.size()) {
@@ -660,28 +552,19 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         }else {
             channels = deviceChannels.subList(index, deviceChannels.size());
         }
-        try {
-            Integer finalIndex = index;
-            String catalogXmlContent = getCatalogXmlContentForCatalogOther(parentPlatform, channels, type);
-            sendNotify(parentPlatform, catalogXmlContent, subscribeInfo, eventResult -> {
-                logger.error("发送NOTIFY通知消息失败。错误:{} {}", eventResult.statusCode, eventResult.msg);
-            }, (eventResult -> {
+        Integer finalIndex = index;
+        String catalogXmlContent = getCatalogXmlContentForCatalogOther(parentPlatform, channels, type);
+        sendNotify(parentPlatform, catalogXmlContent, subscribeInfo, eventResult -> {
+            logger.error("发送NOTIFY通知消息失败。错误:{} {}", eventResult.statusCode, eventResult.msg);
+        }, eventResult -> {
+            try {
                 sendNotifyForCatalogOther(type, parentPlatform, deviceChannels, subscribeInfo,
                         finalIndex + parentPlatform.getCatalogGroup());
-            }));
-        } catch (SipException e) {
-            e.printStackTrace();
-        } catch (ParseException e) {
-            e.printStackTrace();
-        } catch (NoSuchFieldException e) {
-            e.printStackTrace();
-        } catch (IllegalAccessException e) {
-            e.printStackTrace();
-        } catch (InvalidArgumentException e) {
-            e.printStackTrace();
-        }
-
-        return true;
+            } catch (InvalidArgumentException | ParseException | NoSuchFieldException | SipException |
+                     IllegalAccessException e) {
+                logger.error("[命令发送失败] 国标级联 NOTIFY通知: {}", e.getMessage());
+            }
+        });
     }
 
     private String getCatalogXmlContentForCatalogOther(ParentPlatform parentPlatform, List<DeviceChannel> channels, String type) {
@@ -711,113 +594,81 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         return catalogXml.toString();
     }
     @Override
-    public boolean recordInfo(DeviceChannel deviceChannel, ParentPlatform parentPlatform, String fromTag, RecordInfo recordInfo) {
+    public void recordInfo(DeviceChannel deviceChannel, ParentPlatform parentPlatform, String fromTag, RecordInfo recordInfo) throws SipException, InvalidArgumentException, ParseException {
         if ( parentPlatform ==null) {
-            return false;
+            return ;
         }
-        try {
-            String characterSet = parentPlatform.getCharacterSet();
-            StringBuffer recordXml = new StringBuffer(600);
-            recordXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-            recordXml.append("<Response>\r\n");
-            recordXml.append("<CmdType>RecordInfo</CmdType>\r\n");
-            recordXml.append("<SN>" +recordInfo.getSn() + "</SN>\r\n");
-            recordXml.append("<DeviceID>" + recordInfo.getDeviceId() + "</DeviceID>\r\n");
-            recordXml.append("<SumNum>" + recordInfo.getSumNum() + "</SumNum>\r\n");
-            if (recordInfo.getRecordList() == null ) {
-                recordXml.append("<RecordList Num=\"0\">\r\n");
-            }else {
-                recordXml.append("<RecordList Num=\"" + recordInfo.getRecordList().size()+"\">\r\n");
-                if (recordInfo.getRecordList().size() > 0) {
-                    for (RecordItem recordItem : recordInfo.getRecordList()) {
-                        recordXml.append("<Item>\r\n");
-                        if (deviceChannel != null) {
-                            recordXml.append("<DeviceID>" + recordItem.getDeviceId() + "</DeviceID>\r\n");
-                            recordXml.append("<Name>" + recordItem.getName() + "</Name>\r\n");
-                            recordXml.append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(recordItem.getStartTime()) + "</StartTime>\r\n");
-                            recordXml.append("<EndTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(recordItem.getEndTime()) + "</EndTime>\r\n");
-                            recordXml.append("<Secrecy>" + recordItem.getSecrecy() + "</Secrecy>\r\n");
-                            recordXml.append("<Type>" + recordItem.getType() + "</Type>\r\n");
-                            if (!ObjectUtils.isEmpty(recordItem.getFileSize())) {
-                                recordXml.append("<FileSize>" + recordItem.getFileSize() + "</FileSize>\r\n");
-                            }
-                            if (!ObjectUtils.isEmpty(recordItem.getFilePath())) {
-                                recordXml.append("<FilePath>" + recordItem.getFilePath() + "</FilePath>\r\n");
-                            }
+        String characterSet = parentPlatform.getCharacterSet();
+        StringBuffer recordXml = new StringBuffer(600);
+        recordXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
+        recordXml.append("<Response>\r\n");
+        recordXml.append("<CmdType>RecordInfo</CmdType>\r\n");
+        recordXml.append("<SN>" +recordInfo.getSn() + "</SN>\r\n");
+        recordXml.append("<DeviceID>" + recordInfo.getDeviceId() + "</DeviceID>\r\n");
+        recordXml.append("<SumNum>" + recordInfo.getSumNum() + "</SumNum>\r\n");
+        if (recordInfo.getRecordList() == null ) {
+            recordXml.append("<RecordList Num=\"0\">\r\n");
+        }else {
+            recordXml.append("<RecordList Num=\"" + recordInfo.getRecordList().size()+"\">\r\n");
+            if (recordInfo.getRecordList().size() > 0) {
+                for (RecordItem recordItem : recordInfo.getRecordList()) {
+                    recordXml.append("<Item>\r\n");
+                    if (deviceChannel != null) {
+                        recordXml.append("<DeviceID>" + recordItem.getDeviceId() + "</DeviceID>\r\n");
+                        recordXml.append("<Name>" + recordItem.getName() + "</Name>\r\n");
+                        recordXml.append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(recordItem.getStartTime()) + "</StartTime>\r\n");
+                        recordXml.append("<EndTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(recordItem.getEndTime()) + "</EndTime>\r\n");
+                        recordXml.append("<Secrecy>" + recordItem.getSecrecy() + "</Secrecy>\r\n");
+                        recordXml.append("<Type>" + recordItem.getType() + "</Type>\r\n");
+                        if (!ObjectUtils.isEmpty(recordItem.getFileSize())) {
+                            recordXml.append("<FileSize>" + recordItem.getFileSize() + "</FileSize>\r\n");
+                        }
+                        if (!ObjectUtils.isEmpty(recordItem.getFilePath())) {
+                            recordXml.append("<FilePath>" + recordItem.getFilePath() + "</FilePath>\r\n");
                         }
-                        recordXml.append("</Item>\r\n");
                     }
+                    recordXml.append("</Item>\r\n");
                 }
             }
+        }
 
-            recordXml.append("</RecordList>\r\n");
-            recordXml.append("</Response>\r\n");
+        recordXml.append("</RecordList>\r\n");
+        recordXml.append("</Response>\r\n");
 
-            // callid
-            CallIdHeader callIdHeader = parentPlatform.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
-                    : udpSipProvider.getNewCallId();
-            Request request = headerProviderPlatformProvider.createMessageRequest(parentPlatform, recordXml.toString(), fromTag, SipUtils.getNewViaTag(), callIdHeader);
-            transmitRequest(parentPlatform, request);
+        // callid
+        CallIdHeader callIdHeader = parentPlatform.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
+                : udpSipProvider.getNewCallId();
+        Request request = headerProviderPlatformProvider.createMessageRequest(parentPlatform, recordXml.toString(), fromTag, SipUtils.getNewViaTag(), callIdHeader);
+        transmitRequest(parentPlatform, request);
 
-        } catch (SipException | ParseException | InvalidArgumentException e) {
-            e.printStackTrace();
-            return false;
-        }
-        return true;
     }
 
     @Override
-    public boolean sendMediaStatusNotify(ParentPlatform platform, SendRtpItem sendRtpItem) {
-        if (sendRtpItem == null) {
-            return false;
-        }
-        if (platform == null) {
-            return false;
-        }
-
-        try{
-
-            String characterSet = platform.getCharacterSet();
-            StringBuffer mediaStatusXml = new StringBuffer(200);
-            mediaStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-            mediaStatusXml.append("<Notify>\r\n");
-            mediaStatusXml.append("<CmdType>MediaStatus</CmdType>\r\n");
-            mediaStatusXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-            mediaStatusXml.append("<DeviceID>" + sendRtpItem.getChannelId() + "</DeviceID>\r\n");
-            mediaStatusXml.append("<NotifyType>121</NotifyType>\r\n");
-            mediaStatusXml.append("</Notify>\r\n");
-
-            SIPRequest messageRequest = (SIPRequest)headerProviderPlatformProvider.createMessageRequest(platform, mediaStatusXml.toString(),
-                    sendRtpItem);
-
-            ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
-            messageRequest.setContent(mediaStatusXml.toString(), contentTypeHeader);
-            SipURI sipURI = (SipURI) messageRequest.getRequestURI();
-            sipURI.setHost(platform.getServerIP());
-            sipURI.setPort(platform.getServerPort());
-            ClientTransaction clientTransaction;
-            if ("TCP".equals(platform.getTransport())) {
-                clientTransaction = tcpSipProvider.getNewClientTransaction(messageRequest);
-            }else {
-                clientTransaction = udpSipProvider.getNewClientTransaction(messageRequest);
-            }
-            clientTransaction.sendRequest();
-        } catch (SipException e) {
-            e.printStackTrace();
-            return false;
-        } catch (ParseException e) {
-            e.printStackTrace();
-            return false;
-        } catch (InvalidArgumentException e) {
-            throw new RuntimeException(e);
+    public void sendMediaStatusNotify(ParentPlatform platform, SendRtpItem sendRtpItem) throws SipException, InvalidArgumentException, ParseException {
+        if (sendRtpItem == null || platform == null) {
+            return;
         }
-        return true;
 
 
+        String characterSet = platform.getCharacterSet();
+        StringBuffer mediaStatusXml = new StringBuffer(200);
+        mediaStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
+        mediaStatusXml.append("<Notify>\r\n");
+        mediaStatusXml.append("<CmdType>MediaStatus</CmdType>\r\n");
+        mediaStatusXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
+        mediaStatusXml.append("<DeviceID>" + sendRtpItem.getChannelId() + "</DeviceID>\r\n");
+        mediaStatusXml.append("<NotifyType>121</NotifyType>\r\n");
+        mediaStatusXml.append("</Notify>\r\n");
+
+        SIPRequest messageRequest = (SIPRequest)headerProviderPlatformProvider.createMessageRequest(platform, mediaStatusXml.toString(),
+                sendRtpItem);
+
+        transmitRequest(platform, messageRequest);
+
     }
 
     @Override
-    public void streamByeCmd(ParentPlatform platform, String callId) {
+    public void streamByeCmd(ParentPlatform platform, String callId) throws SipException, InvalidArgumentException, ParseException {
         if (platform == null) {
             return;
         }
@@ -828,7 +679,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
     }
 
     @Override
-    public void streamByeCmd(ParentPlatform platform, SendRtpItem sendRtpItem) {
+    public void streamByeCmd(ParentPlatform platform, SendRtpItem sendRtpItem) throws SipException, InvalidArgumentException, ParseException {
         if (sendRtpItem == null ) {
             logger.info("[向上级发送BYE], sendRtpItem 为NULL");
             return;
@@ -844,25 +695,10 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
             mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc());
             zlmrtpServerFactory.closeRTPServer(mediaServerItem, sendRtpItem.getStreamId());
         }
-        try {
-
-            SIPRequest byeRequest = headerProviderPlatformProvider.createByeRequest(platform, sendRtpItem);
-            if (byeRequest == null) {
-                logger.warn("[向上级发送bye]:无法创建 byeRequest");
-            }
-            ClientTransaction clientTransaction;
-            if ("TCP".equals(platform.getTransport())) {
-                clientTransaction = tcpSipProvider.getNewClientTransaction(byeRequest);
-            } else {
-                clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest);
-            }
-            clientTransaction.sendRequest();
-        } catch (SipException e) {
-            e.printStackTrace();
-        } catch (ParseException e) {
-            e.printStackTrace();
-        } catch (InvalidArgumentException e) {
-            throw new RuntimeException(e);
+        SIPRequest byeRequest = headerProviderPlatformProvider.createByeRequest(platform, sendRtpItem);
+        if (byeRequest == null) {
+            logger.warn("[向上级发送bye]:无法创建 byeRequest");
         }
+        transmitRequest(platform,byeRequest);
     }
 }

+ 9 - 8
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorParent.java

@@ -7,6 +7,7 @@ import gov.nist.javax.sip.SipStackImpl;
 import gov.nist.javax.sip.message.SIPRequest;
 import gov.nist.javax.sip.message.SIPResponse;
 import gov.nist.javax.sip.stack.SIPServerTransaction;
+import gov.nist.javax.sip.stack.SIPServerTransactionImpl;
 import org.apache.commons.lang3.ArrayUtils;
 import org.dom4j.Document;
 import org.dom4j.DocumentException;
@@ -57,7 +58,7 @@ public abstract class SIPRequestProcessorParent {
 	 */
 	public ServerTransaction getServerTransaction(RequestEvent evt) {
 		Request request = evt.getRequest();
-		ServerTransaction serverTransaction = evt.getServerTransaction();
+		SIPServerTransactionImpl serverTransaction = (SIPServerTransactionImpl)evt.getServerTransaction();
 		// 判断TCP还是UDP
 		boolean isTcp = false;
 		ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
@@ -65,28 +66,28 @@ public abstract class SIPRequestProcessorParent {
 		if (transport.equalsIgnoreCase("TCP")) {
 			isTcp = true;
 		}
-
+		if (serverTransaction != null && serverTransaction.getOriginalRequest() == null) {
+			serverTransaction.setOriginalRequest((SIPRequest) evt.getRequest());
+		}
 		if (serverTransaction == null) {
 			try {
 				if (isTcp) {
 					SipStackImpl stack = (SipStackImpl)tcpSipProvider.getSipStack();
-					serverTransaction = (SIPServerTransaction) stack.findTransaction((SIPRequest)request, true);
+					serverTransaction = (SIPServerTransactionImpl) stack.findTransaction((SIPRequest)request, true);
 					if (serverTransaction == null) {
-						serverTransaction = tcpSipProvider.getNewServerTransaction(request);
+						serverTransaction = (SIPServerTransactionImpl)tcpSipProvider.getNewServerTransaction(request);
 					}
 				} else {
 					SipStackImpl stack = (SipStackImpl)udpSipProvider.getSipStack();
-					serverTransaction = (SIPServerTransaction) stack.findTransaction((SIPRequest)request, true);
+					serverTransaction = (SIPServerTransactionImpl) stack.findTransaction((SIPRequest)request, true);
 					if (serverTransaction == null) {
-						serverTransaction = udpSipProvider.getNewServerTransaction(request);
+						serverTransaction = (SIPServerTransactionImpl)udpSipProvider.getNewServerTransaction(request);
 					}
 				}
 			} catch (TransactionAlreadyExistsException e) {
 				logger.error(e.getMessage());
 			} catch (TransactionUnavailableException e) {
 				logger.error(e.getMessage());
-			}finally {
-
 			}
 		}
 		return serverTransaction;

+ 10 - 3
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java

@@ -23,12 +23,15 @@ import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
+import javax.sip.InvalidArgumentException;
 import javax.sip.RequestEvent;
+import javax.sip.SipException;
 import javax.sip.address.SipURI;
 import javax.sip.header.CallIdHeader;
 import javax.sip.header.FromHeader;
 import javax.sip.header.HeaderAddress;
 import javax.sip.header.ToHeader;
+import java.text.ParseException;
 import java.util.*;
 
 /**
@@ -95,8 +98,8 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
 		SendRtpItem sendRtpItem =  redisCatchStorage.querySendRTPServer(platformGbId, channelId, null, callIdHeader.getCallId());
 		String is_Udp = sendRtpItem.isTcp() ? "0" : "1";
 		MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
-		logger.info("收到ACK,rtp/{}开始向上级推流, 目标 {}:{},SSRC={}", sendRtpItem.getStreamId(), sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc());
-		Map<String, Object> param = new HashMap<>();
+		logger.info("收到ACK,rtp/{}开始向上级推流, 目标={}:{},SSRC={}", sendRtpItem.getStreamId(), sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc());
+		Map<String, Object> param = new HashMap<>(12);
 		param.put("vhost","__defaultVhost__");
 		param.put("app",sendRtpItem.getApp());
 		param.put("stream",sendRtpItem.getStreamId());
@@ -139,7 +142,11 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
 				// TODO 可能是语音对讲
 			}else {
 				// 向上级平台
-				commanderForPlatform.streamByeCmd(parentPlatform, callIdHeader.getCallId());
+				try {
+					commanderForPlatform.streamByeCmd(parentPlatform, callIdHeader.getCallId());
+				} catch (SipException | InvalidArgumentException | ParseException e) {
+					logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
+				}
 			}
 		}
 	}

+ 23 - 13
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java

@@ -1,6 +1,7 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
 
 import com.genersoft.iot.vmp.common.StreamInfo;
+import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.InviteStreamType;
 import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
@@ -12,12 +13,11 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
 import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import com.genersoft.iot.vmp.service.IDeviceService;
 import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
-import com.genersoft.iot.vmp.utils.SerializeUtils;
-import gov.nist.javax.sip.stack.SIPDialog;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.InitializingBean;
@@ -50,6 +50,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
 	@Autowired
 	private IRedisCatchStorage redisCatchStorage;
 
+	@Autowired
+	private IDeviceService deviceService;
+
 	@Autowired
 	private IVideoManagerStorage storager;
 
@@ -77,9 +80,13 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
 	 */
 	@Override
 	public void process(RequestEvent evt) {
+
 		try {
 			responseAck(getServerTransaction(evt), Response.OK);
-			CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME);
+		} catch (SipException | InvalidArgumentException | ParseException e) {
+			logger.error("[回复BYE信息失败],{}", e.getMessage());
+		}
+		CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME);
 			String platformGbId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(FromHeader.NAME)).getAddress().getURI()).getUser();
 			String channelId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser();
 			SendRtpItem sendRtpItem =  redisCatchStorage.querySendRTPServer(platformGbId, channelId, null, callIdHeader.getCallId());
@@ -99,7 +106,17 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
 				if (totalReaderCount <= 0) {
 					logger.info("[收到bye] {} 无其它观看者,通知设备停止推流", streamId);
 					if (sendRtpItem.getPlayType().equals(InviteStreamType.PLAY)) {
-						cmder.streamByeCmd(sendRtpItem.getDeviceId(), channelId, streamId, null);
+						Device device = deviceService.queryDevice(sendRtpItem.getDeviceId());
+						if (device == null) {
+							logger.info("[收到bye] {} 通知设备停止推流时未找到设备信息", streamId);
+						}
+						try {
+							logger.warn("[停止点播] {}/{}", sendRtpItem.getDeviceId(), channelId);
+							cmder.streamByeCmd(device, channelId, streamId, null);
+						} catch (InvalidArgumentException | ParseException | SipException |
+								 SsrcTransactionNotFoundException e) {
+							logger.error("[收到bye] {} 无其它观看者,通知设备停止推流, 发送BYE失败 {}",streamId, e.getMessage());
+						}
 					}
 					if (sendRtpItem.getPlayType().equals(InviteStreamType.PUSH)) {
 						MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
@@ -120,8 +137,7 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
 				}
 				SsrcTransaction ssrcTransactionForPlay = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, "play", null);
 				if (ssrcTransactionForPlay != null){
-					SIPDialog dialogForPlay = (SIPDialog) SerializeUtils.deSerialize(ssrcTransactionForPlay.getDialog());
-					if (dialogForPlay.getCallId().getCallId().equals(callIdHeader.getCallId())){
+					if (ssrcTransactionForPlay.getCallId().equals(callIdHeader.getCallId())){
 						// 释放ssrc
 						MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransactionForPlay.getMediaServerId());
 						if (mediaServerItem != null) {
@@ -140,12 +156,6 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
 					streamSession.remove(device.getDeviceId(), channelId, ssrcTransactionForPlayBack.getStream());
 				}
 			}
-		} catch (SipException e) {
-			e.printStackTrace();
-		} catch (InvalidArgumentException e) {
-			e.printStackTrace();
-		} catch (ParseException e) {
-			e.printStackTrace();
-		}
+
 	}
 }

+ 5 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java

@@ -341,7 +341,11 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                                 logger.info("Ack 等待超时");
                                 mediaServerService.releaseSsrc(mediaServerItemInUSe.getId(), ssrc);
                                 // 回复bye
-                                cmderFroPlatform.streamByeCmd(platform, callIdHeader.getCallId());
+                                try {
+                                    cmderFroPlatform.streamByeCmd(platform, callIdHeader.getCallId());
+                                } catch (SipException | InvalidArgumentException | ParseException e) {
+                                    logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
+                                }
                             }, 60 * 1000);
                             responseSdpAck(serverTransaction, content.toString(), platform);
 

+ 3 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/info/InfoRequestProcessor.java

@@ -93,7 +93,9 @@ public class InfoRequestProcessor extends SIPRequestProcessorParent implements I
                 responseAck(serverTransaction, Response.NOT_FOUND, "device "+ deviceId +" not found");
                 logger.warn("[设备未找到 ]: {}", deviceId);
                 if (sipSubscribe.getErrorSubscribe(callIdHeader.getCallId()) != null){
-                    SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(new DeviceNotFoundEvent(evt.getDialog()));
+                    DeviceNotFoundEvent deviceNotFoundEvent = new DeviceNotFoundEvent(evt.getDialog());
+                    deviceNotFoundEvent.setCallId(callIdHeader.getCallId());
+                    SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(deviceNotFoundEvent);
                     sipSubscribe.getErrorSubscribe(callIdHeader.getCallId()).response(eventResult);
                 };
             }else {

+ 3 - 3
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageRequestProcessor.java

@@ -89,8 +89,6 @@ public class MessageRequestProcessor extends SIPRequestProcessorParent implement
         ParentPlatform parentPlatform = storage.queryParentPlatByServerGBId(deviceId);
         try {
             if (device != null && parentPlatform != null) {
-
-                logger.warn("[重复]平台与设备编号重复:{}", deviceId);
                 SIPRequest request = (SIPRequest) evt.getRequest();
                 String hostAddress = request.getRemoteAddress().getHostAddress();
                 int remotePort = request.getRemotePort();
@@ -105,7 +103,9 @@ public class MessageRequestProcessor extends SIPRequestProcessorParent implement
                 responseAck(serverTransaction, Response.NOT_FOUND, "device "+ deviceId +" not found");
                 logger.warn("[设备未找到 ]: {}", deviceId);
                 if (sipSubscribe.getErrorSubscribe(callIdHeader.getCallId()) != null){
-                    SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(new DeviceNotFoundEvent(evt.getDialog()));
+                    DeviceNotFoundEvent deviceNotFoundEvent = new DeviceNotFoundEvent(evt.getDialog());
+                    deviceNotFoundEvent.setCallId(callIdHeader.getCallId());
+                    SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(deviceNotFoundEvent);
                     sipSubscribe.getErrorSubscribe(callIdHeader.getCallId()).response(eventResult);
                 };
             }else {

+ 48 - 54
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/cmd/DeviceControlQueryMessageHandler.java

@@ -16,6 +16,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
 import org.springframework.util.StringUtils;
@@ -48,6 +50,10 @@ public class DeviceControlQueryMessageHandler extends SIPRequestProcessorParent
     @Autowired
     private SIPCommanderFroPlatform cmderFroPlatform;
 
+    @Qualifier("taskExecutor")
+    @Autowired
+    private ThreadPoolTaskExecutor taskExecutor;
+
     @Override
     public void afterPropertiesSet() throws Exception {
         controlMessageHandler.addHandler(cmdType, this);
@@ -71,34 +77,30 @@ public class DeviceControlQueryMessageHandler extends SIPRequestProcessorParent
             if (parentPlatform.getServerGBId().equals(targetGBId)) {
                 // 远程启动本平台:需要在重新启动程序后先对SipStack解绑
                 logger.info("执行远程启动本平台命令");
-                cmderFroPlatform.unregister(parentPlatform, null, null);
-
-                Thread restartThread = new Thread(new Runnable() {
-                    @Override
-                    public void run() {
-                        try {
-                            Thread.sleep(3000);
-                            SipProvider up = (SipProvider) SpringBeanFactory.getBean("udpSipProvider");
-                            SipStackImpl stack = (SipStackImpl)up.getSipStack();
-                            stack.stop();
-                            Iterator listener = stack.getListeningPoints();
-                            while (listener.hasNext()) {
-                                stack.deleteListeningPoint((ListeningPoint) listener.next());
-                            }
-                            Iterator providers = stack.getSipProviders();
-                            while (providers.hasNext()) {
-                                stack.deleteSipProvider((SipProvider) providers.next());
-                            }
-                            VManageBootstrap.restart();
-                        } catch (InterruptedException ignored) {
-                        } catch (ObjectInUseException e) {
-                            e.printStackTrace();
+                try {
+                    cmderFroPlatform.unregister(parentPlatform, null, null);
+                } catch (InvalidArgumentException | ParseException | SipException e) {
+                    logger.error("[命令发送失败] 国标级联 注销: {}", e.getMessage());
+                }
+                taskExecutor.execute(()->{
+                    try {
+                        Thread.sleep(3000);
+                        SipProvider up = (SipProvider) SpringBeanFactory.getBean("udpSipProvider");
+                        SipStackImpl stack = (SipStackImpl)up.getSipStack();
+                        stack.stop();
+                        Iterator listener = stack.getListeningPoints();
+                        while (listener.hasNext()) {
+                            stack.deleteListeningPoint((ListeningPoint) listener.next());
                         }
+                        Iterator providers = stack.getSipProviders();
+                        while (providers.hasNext()) {
+                            stack.deleteSipProvider((SipProvider) providers.next());
+                        }
+                        VManageBootstrap.restart();
+                    } catch (InterruptedException | ObjectInUseException e) {
+                        logger.error("[任务执行失败] 服务重启: {}", e.getMessage());
                     }
                 });
-
-                restartThread.setDaemon(false);
-                restartThread.start();
             } else {
                 // 远程启动指定设备
             }
@@ -111,37 +113,29 @@ public class DeviceControlQueryMessageHandler extends SIPRequestProcessorParent
                 try {
                     responseAck(serverTransaction, Response.NOT_FOUND);
                     return;
-                } catch (SipException e) {
-                    e.printStackTrace();
-                } catch (InvalidArgumentException e) {
-                    e.printStackTrace();
-                } catch (ParseException e) {
-                    e.printStackTrace();
+                } catch (SipException | InvalidArgumentException | ParseException e) {
+                    logger.error("[命令发送失败] 错误信息: {}", e.getMessage());
                 }
             }
-            cmder.fronEndCmd(deviceForPlatform, channelId, cmdString, eventResult -> {
-                // 失败的回复
-                try {
-                    responseAck(serverTransaction, eventResult.statusCode, eventResult.msg);
-                } catch (SipException e) {
-                    e.printStackTrace();
-                } catch (InvalidArgumentException e) {
-                    e.printStackTrace();
-                } catch (ParseException e) {
-                    e.printStackTrace();
-                }
-            }, eventResult -> {
-                // 成功的回复
-                try {
-                    responseAck(serverTransaction, eventResult.statusCode);
-                } catch (SipException e) {
-                    e.printStackTrace();
-                } catch (InvalidArgumentException e) {
-                    e.printStackTrace();
-                } catch (ParseException e) {
-                    e.printStackTrace();
-                }
-            });
+            try {
+                cmder.fronEndCmd(deviceForPlatform, channelId, cmdString, eventResult -> {
+                    // 失败的回复
+                    try {
+                        responseAck(serverTransaction, eventResult.statusCode, eventResult.msg);
+                    } catch (SipException | InvalidArgumentException | ParseException e) {
+                        logger.error("[命令发送失败] 云台/前端回复: {}", e.getMessage());
+                    }
+                }, eventResult -> {
+                    // 成功的回复
+                    try {
+                        responseAck(serverTransaction, eventResult.statusCode);
+                    } catch (SipException | InvalidArgumentException | ParseException e) {
+                        logger.error("[命令发送失败] 云台/前端回复: {}", e.getMessage());
+                    }
+                });
+            } catch (InvalidArgumentException | SipException | ParseException e) {
+                logger.error("[命令发送失败] 云台/前端: {}", e.getMessage());
+            }
         }
     }
 }

+ 12 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java

@@ -1,6 +1,7 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.cmd;
 
 import com.genersoft.iot.vmp.common.StreamInfo;
+import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
@@ -89,7 +90,12 @@ public class MediaStatusNotifyMessageHandler extends SIPRequestProcessorParent i
             // 先从会话内查找
             SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, callIdHeader.getCallId(), null);
             if (ssrcTransaction != null) { // 兼容海康 媒体通知 消息from字段不是设备ID的问题
-                cmder.streamByeCmd(device.getDeviceId(), ssrcTransaction.getChannelId(), null, callIdHeader.getCallId());
+
+                try {
+                    cmder.streamByeCmd(device, ssrcTransaction.getChannelId(), null, callIdHeader.getCallId());
+                } catch (InvalidArgumentException | ParseException | SsrcTransactionNotFoundException | SipException e) {
+                    logger.error("[录像流]推送完毕,收到关流通知, 发送BYE失败 {}", e.getMessage());
+                }
 
                 // 如果级联播放,需要给上级发送此通知 TODO 多个上级同时观看一个下级 可能存在停错的问题,需要将点播CallId进行上下级绑定
                 SendRtpItem sendRtpItem =  redisCatchStorage.querySendRTPServer(null, ssrcTransaction.getChannelId(), null, null);
@@ -99,7 +105,11 @@ public class MediaStatusNotifyMessageHandler extends SIPRequestProcessorParent i
                         logger.warn("[级联消息发送]:发送MediaStatus发现上级平台{}不存在", sendRtpItem.getPlatformId());
                         return;
                     }
-                    sipCommanderFroPlatform.sendMediaStatusNotify(parentPlatform, sendRtpItem);
+                    try {
+                        sipCommanderFroPlatform.sendMediaStatusNotify(parentPlatform, sendRtpItem);
+                    } catch (SipException | InvalidArgumentException | ParseException e) {
+                        logger.error("[命令发送失败] 国标级联 录像播放完毕: {}", e.getMessage());
+                    }
                 }
             }
         }

+ 7 - 7
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/DeviceInfoQueryMessageHandler.java

@@ -49,14 +49,14 @@ public class DeviceInfoQueryMessageHandler extends SIPRequestProcessorParent imp
         try {
             // 回复200 OK
             responseAck(getServerTransaction(evt), Response.OK);
-        } catch (SipException e) {
-            e.printStackTrace();
-        } catch (InvalidArgumentException e) {
-            e.printStackTrace();
-        } catch (ParseException e) {
-            e.printStackTrace();
+        } catch (SipException | InvalidArgumentException | ParseException e) {
+            logger.error("[命令发送失败] DeviceInfo查询回复: {}", e.getMessage());
         }
         String sn = rootElement.element("SN").getText();
-        cmderFroPlatform.deviceInfoResponse(parentPlatform, sn, fromHeader.getTag());
+        try {
+            cmderFroPlatform.deviceInfoResponse(parentPlatform, sn, fromHeader.getTag());
+        } catch (SipException | InvalidArgumentException | ParseException e) {
+            logger.error("[命令发送失败] 国标级联 DeviceInfo查询回复: {}", e.getMessage());
+        }
     }
 }

+ 7 - 7
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/DeviceStatusQueryMessageHandler.java

@@ -62,14 +62,14 @@ public class DeviceStatusQueryMessageHandler extends SIPRequestProcessorParent i
         // 回复200 OK
         try {
             responseAck(getServerTransaction(evt), Response.OK);
-        } catch (SipException e) {
-            e.printStackTrace();
-        } catch (InvalidArgumentException e) {
-            e.printStackTrace();
-        } catch (ParseException e) {
-            e.printStackTrace();
+        } catch (SipException | InvalidArgumentException | ParseException e) {
+            logger.error("[命令发送失败] 国标级联 DeviceStatus查询回复200OK: {}", e.getMessage());
         }
         String sn = rootElement.element("SN").getText();
-        cmderFroPlatform.deviceStatusResponse(parentPlatform, sn, fromHeader.getTag());
+        try {
+            cmderFroPlatform.deviceStatusResponse(parentPlatform, sn, fromHeader.getTag());
+        } catch (SipException | InvalidArgumentException | ParseException e) {
+            logger.error("[命令发送失败] 国标级联 DeviceStatus查询回复: {}", e.getMessage());
+        }
     }
 }

+ 29 - 37
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/RecordInfoQueryMessageHandler.java

@@ -103,53 +103,45 @@ public class RecordInfoQueryMessageHandler extends SIPRequestProcessorParent imp
             DeviceChannel deviceChannel = storager.queryChannelInParentPlatform(parentPlatform.getServerGBId(), channelId);
             // 接收录像数据
             recordEndEventListener.addEndEventHandler(deviceChannel.getDeviceId(), channelId, (recordInfo)->{
-                cmderFroPlatform.recordInfo(deviceChannel, parentPlatform, fromHeader.getTag(), recordInfo);
+                try {
+                    cmderFroPlatform.recordInfo(deviceChannel, parentPlatform, fromHeader.getTag(), recordInfo);
+                } catch (SipException | InvalidArgumentException | ParseException e) {
+                    logger.error("[命令发送失败] 国标级联 回复录像数据: {}", e.getMessage());
+                }
             });
-            commander.recordInfoQuery(device, channelId, DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(startTime),
-                    DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(endTime), sn, secrecy, type, (eventResult -> {
-                        // 回复200 OK
-                        try {
-                            responseAck(serverTransaction, Response.OK);
-                        } catch (SipException e) {
-                            e.printStackTrace();
-                        } catch (InvalidArgumentException e) {
-                            e.printStackTrace();
-                        } catch (ParseException e) {
-                            e.printStackTrace();
-                        }
-                    }),(eventResult -> {
-                        // 查询失败
-                        try {
-                            responseAck(serverTransaction, eventResult.statusCode, eventResult.msg);
-                        } catch (SipException e) {
-                            e.printStackTrace();
-                        } catch (InvalidArgumentException e) {
-                            e.printStackTrace();
-                        } catch (ParseException e) {
-                            e.printStackTrace();
-                        }
-                    }));
+            try {
+                commander.recordInfoQuery(device, channelId, DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(startTime),
+                        DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(endTime), sn, secrecy, type, (eventResult -> {
+                            // 回复200 OK
+                            try {
+                                responseAck(serverTransaction, Response.OK);
+                            } catch (SipException | InvalidArgumentException | ParseException e) {
+                                logger.error("[命令发送失败] 录像查询回复: {}", e.getMessage());
+                            }
+                        }),(eventResult -> {
+                            // 查询失败
+                            try {
+                                responseAck(serverTransaction, eventResult.statusCode, eventResult.msg);
+                            } catch (SipException | InvalidArgumentException | ParseException e) {
+                                logger.error("[命令发送失败] 录像查询回复: {}", e.getMessage());
+                            }
+                        }));
+            } catch (InvalidArgumentException | ParseException | SipException e) {
+                logger.error("[命令发送失败] 录像查询: {}", e.getMessage());
+            }
 
         }else if (channelSources.get(1).getCount() > 0) { // 直播流
             // TODO
             try {
                 responseAck(serverTransaction, Response.NOT_IMPLEMENTED); // 回复未实现
-            } catch (SipException e) {
-                e.printStackTrace();
-            } catch (InvalidArgumentException e) {
-                e.printStackTrace();
-            } catch (ParseException e) {
-                e.printStackTrace();
+            } catch (SipException | InvalidArgumentException | ParseException e) {
+                logger.error("[命令发送失败] 录像查询: {}", e.getMessage());
             }
         }else { // 错误的请求
             try {
                 responseAck(serverTransaction, Response.BAD_REQUEST);
-            } catch (SipException e) {
-                e.printStackTrace();
-            } catch (InvalidArgumentException e) {
-                e.printStackTrace();
-            } catch (ParseException e) {
-                e.printStackTrace();
+            } catch (SipException | InvalidArgumentException | ParseException e) {
+                logger.error("[命令发送失败] 录像查询: {}", e.getMessage());
             }
         }
     }

+ 62 - 78
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java

@@ -87,89 +87,73 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp
     public void handForDevice(RequestEvent evt, Device device, Element element) {
         taskQueue.offer(new HandlerCatchData(evt, device, element));
         // 回复200 OK
+        ServerTransaction serverTransaction = getServerTransaction(evt);
         try {
-            ServerTransaction serverTransaction = getServerTransaction(evt);
             responseAck(serverTransaction, Response.OK);
-            if (!taskQueueHandlerRun) {
-                taskQueueHandlerRun = true;
-                taskExecutor.execute(()-> {
-                    while (!taskQueue.isEmpty()) {
-                        HandlerCatchData take = taskQueue.poll();
-                        try {
-                            Element rootElement = getRootElement(take.getEvt(), take.getDevice().getCharset());
-                            if (rootElement == null) {
-                                logger.warn("[ 收到通道 ] content cannot be null, {}", evt.getRequest());
-                                continue;
-                            }
-                            Element deviceListElement = rootElement.element("DeviceList");
-                            Element sumNumElement = rootElement.element("SumNum");
-                            Element snElement = rootElement.element("SN");
-                            if (snElement == null || sumNumElement == null || deviceListElement == null) {
-                                responseAck(serverTransaction, Response.BAD_REQUEST, "xml error");
-                                continue;
-                            }
-                            int sumNum = Integer.parseInt(sumNumElement.getText());
-
-                            if (sumNum == 0) {
-                                logger.info("[收到通道]设备:{}的: 0个", take.getDevice().getDeviceId());
-                                // 数据已经完整接收
-                                storager.cleanChannelsForDevice(take.getDevice().getDeviceId());
-                                catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), null);
-                            }else {
-                                Iterator<Element> deviceListIterator = deviceListElement.elementIterator();
-                                if (deviceListIterator != null) {
-                                    List<DeviceChannel> channelList = new ArrayList<>();
-                                    // 遍历DeviceList
-                                    while (deviceListIterator.hasNext()) {
-                                        Element itemDevice = deviceListIterator.next();
-                                        Element channelDeviceElement = itemDevice.element("DeviceID");
-                                        if (channelDeviceElement == null) {
-                                            continue;
-                                        }
-                                        DeviceChannel deviceChannel = XmlUtil.channelContentHander(itemDevice, device, null);
-                                        deviceChannel.setDeviceId(take.getDevice().getDeviceId());
-
-                                        channelList.add(deviceChannel);
-                                    }
-                                    int sn = Integer.parseInt(snElement.getText());
-                                    catalogDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, take.getDevice(), channelList);
-                                    logger.info("[收到通道]设备: {} -> {}个,{}/{}", take.getDevice().getDeviceId(), channelList.size(), catalogDataCatch.get(take.getDevice().getDeviceId()) == null ? 0 :catalogDataCatch.get(take.getDevice().getDeviceId()).size(), sumNum);
-                                    if (catalogDataCatch.get(take.getDevice().getDeviceId()).size() == sumNum) {
-                                        // 数据已经完整接收, 此时可能存在某个设备离线变上线的情况,但是考虑到性能,此处不做处理,
-                                        // 目前支持设备通道上线通知时和设备上线时向上级通知
-                                        boolean resetChannelsResult = storager.resetChannels(take.getDevice().getDeviceId(), catalogDataCatch.get(take.getDevice().getDeviceId()));
-                                        if (!resetChannelsResult) {
-                                            String errorMsg = "接收成功,写入失败,共" + sumNum + "条,已接收" + catalogDataCatch.get(take.getDevice().getDeviceId()).size() + "条";
-                                            catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), errorMsg);
-                                        }else {
-                                            catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), null);
-                                        }
-                                    }
+        } catch (SipException | InvalidArgumentException | ParseException e) {
+            logger.error("[命令发送失败] 目录查询回复: {}", e.getMessage());
+        }
+        if (!taskQueueHandlerRun) {
+            taskQueueHandlerRun = true;
+            taskExecutor.execute(() -> {
+                while (!taskQueue.isEmpty()) {
+                    HandlerCatchData take = taskQueue.poll();
+                    Element rootElement = null;
+                    try {
+                        rootElement = getRootElement(take.getEvt(), take.getDevice().getCharset());
+                    } catch (DocumentException e) {
+                        logger.error("[xml解析] 失败: ", e);
+                        continue;
+                    }
+                    if (rootElement == null) {
+                        logger.warn("[ 收到通道 ] content cannot be null, {}", evt.getRequest());
+                        continue;
+                    }
+                    Element deviceListElement = rootElement.element("DeviceList");
+                    Element sumNumElement = rootElement.element("SumNum");
+                    Element snElement = rootElement.element("SN");
+                    int sumNum = Integer.parseInt(sumNumElement.getText());
+
+                    if (sumNum == 0) {
+                        logger.info("[收到通道]设备:{}的: 0个", take.getDevice().getDeviceId());
+                        // 数据已经完整接收
+                        storager.cleanChannelsForDevice(take.getDevice().getDeviceId());
+                        catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), null);
+                    } else {
+                        Iterator<Element> deviceListIterator = deviceListElement.elementIterator();
+                        if (deviceListIterator != null) {
+                            List<DeviceChannel> channelList = new ArrayList<>();
+                            // 遍历DeviceList
+                            while (deviceListIterator.hasNext()) {
+                                Element itemDevice = deviceListIterator.next();
+                                Element channelDeviceElement = itemDevice.element("DeviceID");
+                                if (channelDeviceElement == null) {
+                                    continue;
                                 }
+                                DeviceChannel deviceChannel = XmlUtil.channelContentHander(itemDevice, device, null);
+                                deviceChannel.setDeviceId(take.getDevice().getDeviceId());
 
+                                channelList.add(deviceChannel);
+                            }
+                            int sn = Integer.parseInt(snElement.getText());
+                            catalogDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, take.getDevice(), channelList);
+                            logger.info("[收到通道]设备: {} -> {}个,{}/{}", take.getDevice().getDeviceId(), channelList.size(), catalogDataCatch.get(take.getDevice().getDeviceId()) == null ? 0 : catalogDataCatch.get(take.getDevice().getDeviceId()).size(), sumNum);
+                            if (catalogDataCatch.get(take.getDevice().getDeviceId()).size() == sumNum) {
+                                // 数据已经完整接收, 此时可能存在某个设备离线变上线的情况,但是考虑到性能,此处不做处理,
+                                // 目前支持设备通道上线通知时和设备上线时向上级通知
+                                boolean resetChannelsResult = storager.resetChannels(take.getDevice().getDeviceId(), catalogDataCatch.get(take.getDevice().getDeviceId()));
+                                if (!resetChannelsResult) {
+                                    String errorMsg = "接收成功,写入失败,共" + sumNum + "条,已接收" + catalogDataCatch.get(take.getDevice().getDeviceId()).size() + "条";
+                                    catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), errorMsg);
+                                } else {
+                                    catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), null);
+                                }
                             }
-                        } catch (DocumentException e) {
-                            e.printStackTrace();
-                        } catch (InvalidArgumentException e) {
-                            e.printStackTrace();
-                        } catch (ParseException e) {
-                            e.printStackTrace();
-                        } catch (SipException e) {
-                            e.printStackTrace();
-                        } finally {
-                            taskQueueHandlerRun = false;
                         }
+
                     }
-                });
-            }
-        } catch (SipException e) {
-            throw new RuntimeException(e);
-        } catch (InvalidArgumentException e) {
-            throw new RuntimeException(e);
-        } catch (ParseException e) {
-            throw new RuntimeException(e);
-        } finally {
-            taskQueueHandlerRun = false;
+                }
+            });
         }
 
     }
@@ -182,7 +166,7 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp
     public SyncStatus getChannelSyncProgress(String deviceId) {
         if (catalogDataCatch.get(deviceId) == null) {
             return null;
-        }else {
+        } else {
             return catalogDataCatch.getSyncStatus(deviceId);
         }
     }
@@ -190,7 +174,7 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp
     public boolean isSyncRunning(String deviceId) {
         if (catalogDataCatch.get(deviceId) == null) {
             return false;
-        }else {
+        } else {
             return catalogDataCatch.isSyncRunning(deviceId);
         }
     }

+ 19 - 26
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/PresetQueryResponseMessageHandler.java

@@ -50,12 +50,12 @@ public class PresetQueryResponseMessageHandler extends SIPRequestProcessorParent
 
     @Override
     public void handForDevice(RequestEvent evt, Device device, Element element) {
-        Element rootElement = null;
-        try {
 
-            ServerTransaction serverTransaction = getServerTransaction(evt);
+        ServerTransaction serverTransaction = getServerTransaction(evt);
+
+        try {
+             Element rootElement = getRootElement(evt, device.getCharset());
 
-            rootElement = getRootElement(evt, device.getCharset());
             if (rootElement == null) {
                 logger.warn("[ 设备预置位查询应答 ] content cannot be null, {}", evt.getRequest());
                 responseAck(serverTransaction, Response.BAD_REQUEST);
@@ -66,32 +66,29 @@ public class PresetQueryResponseMessageHandler extends SIPRequestProcessorParent
             //该字段可能为通道或则设备的id
             String deviceId = getText(rootElement, "DeviceID");
             String key = DeferredResultHolder.CALLBACK_CMD_PRESETQUERY + deviceId;
-            if (snElement == null ||  presetListNumElement == null) {
+            if (snElement == null || presetListNumElement == null) {
                 responseAck(serverTransaction, Response.BAD_REQUEST, "xml error");
                 return;
             }
             int sumNum = Integer.parseInt(presetListNumElement.attributeValue("Num"));
             List<PresetQuerySipReq> presetQuerySipReqList = new ArrayList<>();
             if (sumNum > 0) {
-                for (Iterator<Element> presetIterator =  presetListNumElement.elementIterator();presetIterator.hasNext();){
+                for (Iterator<Element> presetIterator = presetListNumElement.elementIterator(); presetIterator.hasNext(); ) {
                     Element itemListElement = presetIterator.next();
                     PresetQuerySipReq presetQuerySipReq = new PresetQuerySipReq();
-                    for (Iterator<Element> itemListIterator =  itemListElement.elementIterator();itemListIterator.hasNext();){
-                                // 遍历item
-                                Element itemOne = itemListIterator.next();
-                                String name = itemOne.getName();
-                                String textTrim = itemOne.getTextTrim();
-                                if("PresetID".equalsIgnoreCase(name)){
-                                    presetQuerySipReq.setPresetId(textTrim);
-                                }else {
-                                    presetQuerySipReq.setPresetName(textTrim);
-                                }
+                    for (Iterator<Element> itemListIterator = itemListElement.elementIterator(); itemListIterator.hasNext(); ) {
+                        // 遍历item
+                        Element itemOne = itemListIterator.next();
+                        String name = itemOne.getName();
+                        String textTrim = itemOne.getTextTrim();
+                        if ("PresetID".equalsIgnoreCase(name)) {
+                            presetQuerySipReq.setPresetId(textTrim);
+                        } else {
+                            presetQuerySipReq.setPresetName(textTrim);
+                        }
                     }
                     presetQuerySipReqList.add(presetQuerySipReq);
-
-
                 }
-
             }
             RequestMessage requestMessage = new RequestMessage();
             requestMessage.setKey(key);
@@ -99,13 +96,9 @@ public class PresetQueryResponseMessageHandler extends SIPRequestProcessorParent
             deferredResultHolder.invokeAllResult(requestMessage);
             responseAck(serverTransaction, Response.OK);
         } catch (DocumentException e) {
-            e.printStackTrace();
-        } catch (InvalidArgumentException e) {
-            e.printStackTrace();
-        } catch (ParseException e) {
-            e.printStackTrace();
-        } catch (SipException e) {
-            e.printStackTrace();
+            logger.error("[解析xml]失败: ", e);
+        } catch (InvalidArgumentException | ParseException | SipException e) {
+            logger.error("[命令发送失败] 设备预置位查询应答处理: {}", e.getMessage());
         }
     }
 

+ 32 - 25
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java

@@ -1,18 +1,27 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.response.impl;
 
 import com.genersoft.iot.vmp.conf.SipConfig;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
 import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
+import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
+import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider;
 import com.genersoft.iot.vmp.gb28181.transmit.event.response.SIPResponseProcessorAbstract;
 import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
+import com.genersoft.iot.vmp.service.IDeviceService;
 import com.genersoft.iot.vmp.utils.GitUtil;
 import gov.nist.javax.sip.ResponseEventExt;
+import gov.nist.javax.sip.SipProviderImpl;
 import gov.nist.javax.sip.message.SIPResponse;
 import gov.nist.javax.sip.stack.SIPClientTransaction;
 import gov.nist.javax.sip.stack.SIPDialog;
+import gov.nist.javax.sip.stack.SIPTransaction;
+import gov.nist.javax.sip.stack.SIPTransactionImpl;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Component;
 
 import javax.sdp.SdpFactory;
@@ -54,6 +63,20 @@ public class InviteResponseProcessor extends SIPResponseProcessorAbstract {
 	@Autowired
 	private GitUtil gitUtil;
 
+	@Autowired
+	private ISIPCommander commander;
+
+	@Autowired
+	private IDeviceService deviceService;
+
+	@Autowired
+	private SIPRequestHeaderProvider headerProvider;
+
+	@Autowired
+	@Qualifier(value="udpSipProvider")
+	private SipProviderImpl udpSipProvider;
+
+
 	@Override
 	public void afterPropertiesSet() throws Exception {
 		// 添加消息处理的订阅
@@ -71,7 +94,8 @@ public class InviteResponseProcessor extends SIPResponseProcessorAbstract {
 	@Override
 	public void process(ResponseEvent evt ){
 		try {
-			Response response = evt.getResponse();
+
+			SIPResponse response = (SIPResponse)evt.getResponse();
 			int statusCode = response.getStatusCode();
 			// trying不会回复
 			if (statusCode == Response.TRYING) {
@@ -80,10 +104,7 @@ public class InviteResponseProcessor extends SIPResponseProcessorAbstract {
 			// 下发ack
 			if (statusCode == Response.OK) {
 				ResponseEventExt event = (ResponseEventExt)evt;
-				SIPDialog dialog = new SIPDialog((SIPClientTransaction) event.getClientTransaction(), (SIPResponse) event.getResponse());
-				CSeqHeader cseq = (CSeqHeader) response.getHeader(CSeqHeader.NAME);
-				Request reqAck = dialog.createAck(cseq.getSeqNumber());
-				SipURI requestURI = (SipURI) reqAck.getRequestURI();
+
 				String contentString = new String(response.getRawContent());
 				// jainSip不支持y=字段, 移除以解析。
 				int ssrcIndex = contentString.indexOf("y=");
@@ -96,29 +117,15 @@ public class InviteResponseProcessor extends SIPResponseProcessorAbstract {
 				} else {
 					sdp = SdpFactory.getInstance().createSessionDescription(contentString);
 				}
-				requestURI.setUser(sdp.getOrigin().getUsername());
-				try {
-					requestURI.setHost(event.getRemoteIpAddress());
-				} catch (ParseException e) {
-					e.printStackTrace();
-				}
-				requestURI.setPort(event.getRemotePort());
-				reqAck.setRequestURI(requestURI);
-				UserAgentHeader userAgentHeader = SipUtils.createUserAgentHeader(sipFactory, gitUtil);
-				reqAck.addHeader(userAgentHeader);
-				Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getIp()+":"+sipConfig.getPort()));
-				reqAck.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
-				logger.info("[回复ack] {}-> {}:{} ",requestURI, event.getRemoteIpAddress(), event.getRemotePort());
+				SipURI requestUri = sipFactory.createAddressFactory().createSipURI(sdp.getOrigin().getUsername(), event.getRemoteIpAddress() + ":" + event.getRemotePort());
+				Request reqAck = headerProvider.createAckRequest(requestUri, response);
 
-				dialog.sendAck(reqAck);
+				logger.info("[回复ack] {}-> {}:{} ", sdp.getOrigin().getUsername(), event.getRemoteIpAddress(), event.getRemotePort());
+				commander.transmitRequest(response.getTopmostViaHeader().getTransport(), reqAck, null, null);
 
 			}
-		} catch (InvalidArgumentException | SipException e) {
-			e.printStackTrace();
-		} catch (ParseException e) {
-			throw new RuntimeException(e);
-		} catch (SdpParseException e) {
-			throw new RuntimeException(e);
+		} catch (InvalidArgumentException | ParseException | SipException | SdpParseException e) {
+			logger.info("[点播回复ACK],异常:", e );
 		}
 	}
 

+ 8 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/RegisterResponseProcessor.java

@@ -15,10 +15,13 @@ import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
+import javax.sip.InvalidArgumentException;
 import javax.sip.ResponseEvent;
+import javax.sip.SipException;
 import javax.sip.header.CallIdHeader;
 import javax.sip.header.WWWAuthenticateHeader;
 import javax.sip.message.Response;
+import java.text.ParseException;
 
 /**    
  * @description:Register响应处理器
@@ -87,7 +90,11 @@ public class RegisterResponseProcessor extends SIPResponseProcessorAbstract {
 
 		if (response.getStatusCode() == Response.UNAUTHORIZED) {
 			WWWAuthenticateHeader www = (WWWAuthenticateHeader)response.getHeader(WWWAuthenticateHeader.NAME);
-			sipCommanderForPlatform.register(parentPlatform, callId, www, null, null, true, platformRegisterInfo.isRegister());
+			try {
+				sipCommanderForPlatform.register(parentPlatform, callId, www, null, null, true, platformRegisterInfo.isRegister());
+			} catch (SipException | InvalidArgumentException | ParseException e) {
+				logger.error("[命令发送失败] 国标级联 再次注册: {}", e.getMessage());
+			}
 		}else if (response.getStatusCode() == Response.OK){
 
 			if (platformRegisterInfo.isRegister()) {

+ 43 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java

@@ -70,4 +70,47 @@ public class SipUtils {
         return String.valueOf(System.currentTimeMillis());
     }
 
+
+    /**
+     * 云台指令码计算
+     *
+     * @param leftRight  镜头左移右移 0:停止 1:左移 2:右移
+     * @param upDown     镜头上移下移 0:停止 1:上移 2:下移
+     * @param inOut      镜头放大缩小 0:停止 1:缩小 2:放大
+     * @param moveSpeed  镜头移动速度 默认 0XFF (0-255)
+     * @param zoomSpeed  镜头缩放速度 默认 0X1 (0-255)
+     */
+    public static String cmdString(int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed) {
+        int cmdCode = 0;
+        if (leftRight == 2) {
+            cmdCode|=0x01;		// 右移
+        } else if(leftRight == 1) {
+            cmdCode|=0x02;		// 左移
+        }
+        if (upDown == 2) {
+            cmdCode|=0x04;		// 下移
+        } else if(upDown == 1) {
+            cmdCode|=0x08;		// 上移
+        }
+        if (inOut == 2) {
+            cmdCode |= 0x10;	// 放大
+        } else if(inOut == 1) {
+            cmdCode |= 0x20;	// 缩小
+        }
+        StringBuilder builder = new StringBuilder("A50F01");
+        String strTmp;
+        strTmp = String.format("%02X", cmdCode);
+        builder.append(strTmp, 0, 2);
+        strTmp = String.format("%02X", moveSpeed);
+        builder.append(strTmp, 0, 2);
+        builder.append(strTmp, 0, 2);
+        strTmp = String.format("%X", zoomSpeed);
+        builder.append(strTmp, 0, 1).append("0");
+        //计算校验码
+        int checkCode = (0XA5 + 0X0F + 0X01 + cmdCode + moveSpeed + moveSpeed + (zoomSpeed /*<< 4*/ & 0XF0)) % 0X100;
+        strTmp = String.format("%02X", checkCode);
+        builder.append(strTmp, 0, 2);
+        return builder.toString();
+    }
+
 }

+ 42 - 7
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java

@@ -1,5 +1,6 @@
 package com.genersoft.iot.vmp.media.zlm;
 
+import java.text.ParseException;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -7,6 +8,7 @@ import java.util.Map;
 import com.alibaba.fastjson.JSON;
 import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.conf.UserSetting;
+import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
@@ -31,6 +33,8 @@ import com.alibaba.fastjson.JSONObject;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
 
 import javax.servlet.http.HttpServletRequest;
+import javax.sip.InvalidArgumentException;
+import javax.sip.SipException;
 
 /**    
  * @description:针对 ZLMediaServer的hook事件监听
@@ -58,6 +62,9 @@ public class ZLMHttpHookListener {
 	@Autowired
 	private IRedisCatchStorage redisCatchStorage;
 
+	@Autowired
+	private IDeviceService deviceService;
+
 	@Autowired
 	private IMediaServerService mediaServerService;
 
@@ -515,7 +522,11 @@ public class ZLMHttpHookListener {
 							String platformId = sendRtpItem.getPlatformId();
 							ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId);
 
-							commanderFroPlatform.streamByeCmd(platform, sendRtpItem);
+							try {
+								commanderFroPlatform.streamByeCmd(platform, sendRtpItem);
+							} catch (SipException | InvalidArgumentException | ParseException e) {
+								logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
+							}
 						}
 					}
 				}
@@ -552,21 +563,41 @@ public class ZLMHttpHookListener {
 					if (sendRtpItems.size() > 0) {
 						for (SendRtpItem sendRtpItem : sendRtpItems) {
 							ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
-							commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId());
+							try {
+								commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId());
+							} catch (SipException | InvalidArgumentException | ParseException e) {
+								logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
+							}
 							redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(),
 									sendRtpItem.getCallId(), sendRtpItem.getStreamId());
 						}
 					}
 				}
-				cmder.streamByeCmd(streamInfoForPlayCatch.getDeviceID(), streamInfoForPlayCatch.getChannelId(),
-						streamInfoForPlayCatch.getStream(), null);
+				Device device = deviceService.queryDevice(streamInfoForPlayCatch.getDeviceID());
+				if (device != null) {
+					try {
+						cmder.streamByeCmd(device, streamInfoForPlayCatch.getChannelId(),
+								streamInfoForPlayCatch.getStream(), null);
+					} catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) {
+						logger.error("[无人观看]点播, 发送BYE失败 {}", e.getMessage());
+					}
+				}
+
 				redisCatchStorage.stopPlay(streamInfoForPlayCatch);
 				storager.stopPlay(streamInfoForPlayCatch.getDeviceID(), streamInfoForPlayCatch.getChannelId());
 			}else{
 				StreamInfo streamInfoForPlayBackCatch = redisCatchStorage.queryPlayback(null, null, streamId, null);
 				if (streamInfoForPlayBackCatch != null) {
-					cmder.streamByeCmd(streamInfoForPlayBackCatch.getDeviceID(),
-							streamInfoForPlayBackCatch.getChannelId(), streamInfoForPlayBackCatch.getStream(), null);
+					Device device = deviceService.queryDevice(streamInfoForPlayCatch.getDeviceID());
+					if (device != null) {
+						try {
+							cmder.streamByeCmd(device,streamInfoForPlayBackCatch.getChannelId(),
+									streamInfoForPlayBackCatch.getStream(), null);
+						} catch (InvalidArgumentException | ParseException | SipException |
+								 SsrcTransactionNotFoundException e) {
+							logger.error("[无人观看]回放, 发送BYE失败 {}", e.getMessage());
+						}
+					}
 					redisCatchStorage.stopPlayback(streamInfoForPlayBackCatch.getDeviceID(),
 							streamInfoForPlayBackCatch.getChannelId(), streamInfoForPlayBackCatch.getStream(), null);
 				}else {
@@ -689,7 +720,11 @@ public class ZLMHttpHookListener {
 		if (sendRtpItems.size() > 0) {
 			for (SendRtpItem sendRtpItem : sendRtpItems) {
 				ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
-				commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId());
+				try {
+					commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId());
+				} catch (SipException | InvalidArgumentException | ParseException e) {
+					logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
+				}
 				redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(),
 						sendRtpItem.getCallId(), sendRtpItem.getStreamId());
 			}

+ 11 - 12
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java

@@ -68,7 +68,6 @@ public class ZLMMediaListManager {
     private Map<String, ChannelOnlineEvent> channelOnPublishEvents = new ConcurrentHashMap<>();
 
     public StreamPushItem addPush(MediaItem mediaItem) {
-        // 查找此直播流是否存在redis预设gbId
         StreamPushItem transform = streamPushService.transform(mediaItem);
         StreamPushItem pushInDb = streamPushService.getPush(mediaItem.getApp(), mediaItem.getStream());
         transform.setPushIng(mediaItem.isRegist());
@@ -82,15 +81,14 @@ public class ZLMMediaListManager {
             streamPushMapper.update(transform);
             gbStreamMapper.updateMediaServer(mediaItem.getApp(), mediaItem.getStream(), mediaItem.getMediaServerId());
         }
-        if (transform != null) {
-            if (getChannelOnlineEventLister(transform.getApp(), transform.getStream()) != null)  {
-                try {
-                    getChannelOnlineEventLister(transform.getApp(), transform.getStream()).run(transform.getApp(), transform.getStream(), transform.getServerId());
-                } catch (ParseException e) {
-                    throw new RuntimeException(e);
-                }
-                removedChannelOnlineEventLister(transform.getApp(), transform.getStream());
+        ChannelOnlineEvent channelOnlineEventLister = getChannelOnlineEventLister(transform.getApp(), transform.getStream());
+        if ( channelOnlineEventLister != null)  {
+            try {
+                channelOnlineEventLister.run(transform.getApp(), transform.getStream(), transform.getServerId());;
+            } catch (ParseException e) {
+                logger.error("addPush: ", e);
             }
+            removedChannelOnlineEventLister(transform.getApp(), transform.getStream());
         }
         return transform;
     }
@@ -99,11 +97,12 @@ public class ZLMMediaListManager {
         MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
         // 查看推流状态
         if (zlmrtpServerFactory.isStreamReady(mediaServerItem, app, stream)) {
-            if (getChannelOnlineEventLister(app, stream) != null)  {
+            ChannelOnlineEvent channelOnlineEventLister = getChannelOnlineEventLister(app, stream);
+            if (channelOnlineEventLister != null)  {
                 try {
-                    getChannelOnlineEventLister(app, stream).run(app, stream, mediaServerId);
+                    channelOnlineEventLister.run(app, stream, mediaServerId);
                 } catch (ParseException e) {
-                    throw new RuntimeException(e);
+                    logger.error("sendStreamEvent: ", e);
                 }
                 removedChannelOnlineEventLister(app, stream);
             }

+ 36 - 7
src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java

@@ -26,6 +26,9 @@ import org.springframework.stereotype.Service;
 import org.springframework.util.ObjectUtils;
 import org.springframework.util.StringUtils;
 
+import javax.sip.InvalidArgumentException;
+import javax.sip.SipException;
+import java.text.ParseException;
 import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -95,7 +98,11 @@ public class DeviceServiceImpl implements IDeviceService {
             logger.info("[设备上线,首次注册]: {},查询设备信息以及通道信息", device.getDeviceId());
             deviceMapper.add(device);
             redisCatchStorage.updateDevice(device);
-            commander.deviceInfoQuery(device);
+            try {
+                commander.deviceInfoQuery(device);
+            } catch (InvalidArgumentException | SipException | ParseException e) {
+                logger.error("[命令发送失败] 查询设备信息: {}", e.getMessage());
+            }
             sync(device);
         }else {
             if(device.getOnline() == 0){
@@ -104,7 +111,11 @@ public class DeviceServiceImpl implements IDeviceService {
                 logger.info("[设备上线,离线状态下重新注册]: {},查询设备信息以及通道信息", device.getDeviceId());
                 deviceMapper.update(device);
                 redisCatchStorage.updateDevice(device);
-                commander.deviceInfoQuery(device);
+                try {
+                    commander.deviceInfoQuery(device);
+                } catch (InvalidArgumentException | SipException | ParseException e) {
+                    logger.error("[命令发送失败] 查询设备信息: {}", e.getMessage());
+                }
                 sync(device);
                 // TODO 如果设备下的通道级联到了其他平台,那么需要发送事件或者notify给上级平台
             }else {
@@ -129,6 +140,7 @@ public class DeviceServiceImpl implements IDeviceService {
 
     @Override
     public void offline(String deviceId) {
+        logger.info("[设备离线], device:{}", deviceId);
         Device device = deviceMapper.getDeviceByDeviceId(deviceId);
         if (device == null) {
             return;
@@ -238,15 +250,28 @@ public class DeviceServiceImpl implements IDeviceService {
         }
         int sn = (int)((Math.random()*9+1)*100000);
         catalogResponseMessageHandler.setChannelSyncReady(device, sn);
-        sipCommander.catalogQuery(device, sn, event -> {
-            String errorMsg = String.format("同步通道失败,错误码: %s, %s", event.statusCode, event.msg);
+        try {
+            sipCommander.catalogQuery(device, sn, event -> {
+                String errorMsg = String.format("同步通道失败,错误码: %s, %s", event.statusCode, event.msg);
+                catalogResponseMessageHandler.setChannelSyncEnd(device.getDeviceId(), errorMsg);
+            });
+        } catch (SipException | InvalidArgumentException | ParseException e) {
+            logger.error("[同步通道], 信令发送失败:{}", e.getMessage() );
+            String errorMsg = String.format("同步通道失败,信令发送失败: %s", e.getMessage());
             catalogResponseMessageHandler.setChannelSyncEnd(device.getDeviceId(), errorMsg);
-        });
+        }
     }
 
     @Override
     public Device queryDevice(String deviceId) {
-        return deviceMapper.getDeviceByDeviceId(deviceId);
+        Device device = redisCatchStorage.getDevice(deviceId);
+        if (device == null) {
+            device = deviceMapper.getDeviceByDeviceId(deviceId);
+            if (device != null) {
+                redisCatchStorage.updateDevice(device);
+            }
+        }
+        return device;
     }
 
     @Override
@@ -266,7 +291,11 @@ public class DeviceServiceImpl implements IDeviceService {
         if (device == null || device.getOnline() == 0) {
             return;
         }
-        sipCommander.deviceStatusQuery(device, null);
+        try {
+            sipCommander.deviceStatusQuery(device, null);
+        } catch (InvalidArgumentException | SipException | ParseException e) {
+            logger.error("[命令发送失败] 设备状态查询: {}", e.getMessage());
+        }
 
     }
 

+ 75 - 43
src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java

@@ -20,7 +20,10 @@ import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import javax.sip.InvalidArgumentException;
+import javax.sip.SipException;
 import javax.sip.TimeoutEvent;
+import java.text.ParseException;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -99,9 +102,13 @@ public class PlatformServiceImpl implements IPlatformService {
         if (parentPlatform.isEnable()) {
             // 保存时启用就发送注册
             // 注册成功时由程序直接调用了online方法
-            commanderForPlatform.register(parentPlatform, eventResult -> {
-                logger.info("[国标级联] {},添加向上级注册失败,请确定上级平台可用时重新保存", parentPlatform.getServerGBId());
-            }, null);
+            try {
+                commanderForPlatform.register(parentPlatform, eventResult -> {
+                    logger.info("[国标级联] {},添加向上级注册失败,请确定上级平台可用时重新保存", parentPlatform.getServerGBId());
+                }, null);
+            } catch (InvalidArgumentException | ParseException | SipException e) {
+                logger.error("[命令发送失败] 国标级联: {}", e.getMessage());
+            }
         }
         return result > 0;
     }
@@ -130,46 +137,62 @@ public class PlatformServiceImpl implements IPlatformService {
         // 添加注册任务
         dynamicTask.startDelay(registerTaskKey,
                 // 注册失败(注册成功时由程序直接调用了online方法)
-                ()->commanderForPlatform.register(parentPlatform, eventResult -> offline(parentPlatform),null),
+                ()-> {
+                    try {
+                        commanderForPlatform.register(parentPlatform, eventResult -> offline(parentPlatform),null);
+                    } catch (InvalidArgumentException | ParseException | SipException e) {
+                        logger.error("[命令发送失败] 国标级联定时注册: {}", e.getMessage());
+                    }
+                },
                 (parentPlatform.getExpires() - 10) *1000);
 
         final String keepaliveTaskKey = KEEPALIVE_KEY_PREFIX + parentPlatform.getServerGBId();
         if (!dynamicTask.contains(keepaliveTaskKey)) {
             // 添加心跳任务
             dynamicTask.startCron(keepaliveTaskKey,
-                    ()-> commanderForPlatform.keepalive(parentPlatform, eventResult -> {
-                        // 心跳失败
-                        if (eventResult.type == SipSubscribe.EventResultType.timeout) {
-                            // 心跳超时
-                            ParentPlatformCatch platformCatch = redisCatchStorage.queryPlatformCatchInfo(parentPlatform.getServerGBId());
-                            // 此时是第三次心跳超时, 平台离线
-                            if (platformCatch.getKeepAliveReply()  == 2) {
-                                // 设置平台离线,并重新注册
-                                offline(parentPlatform);
-                                logger.info("[国标级联] {},三次心跳超时后再次发起注册", parentPlatform.getServerGBId());
-                                commanderForPlatform.register(parentPlatform, eventResult1 -> {
-                                    logger.info("[国标级联] {},三次心跳超时后再次发起注册仍然失败,开始定时发起注册,间隔为1分钟", parentPlatform.getServerGBId());
-                                    // 添加注册任务
-                                    dynamicTask.startCron(registerTaskKey,
-                                            // 注册失败(注册成功时由程序直接调用了online方法)
-                                            ()->logger.info("[国标级联] {},平台离线后持续发起注册,失败", parentPlatform.getServerGBId()),
-                                            60*1000);
-                                }, null);
-                            }
+                    ()-> {
+                        try {
+                            commanderForPlatform.keepalive(parentPlatform, eventResult -> {
+                                // 心跳失败
+                                if (eventResult.type == SipSubscribe.EventResultType.timeout) {
+                                    // 心跳超时
+                                    ParentPlatformCatch platformCatch = redisCatchStorage.queryPlatformCatchInfo(parentPlatform.getServerGBId());
+                                    // 此时是第三次心跳超时, 平台离线
+                                    if (platformCatch.getKeepAliveReply()  == 2) {
+                                        // 设置平台离线,并重新注册
+                                        offline(parentPlatform);
+                                        logger.info("[国标级联] {},三次心跳超时后再次发起注册", parentPlatform.getServerGBId());
+                                        try {
+                                            commanderForPlatform.register(parentPlatform, eventResult1 -> {
+                                                logger.info("[国标级联] {},三次心跳超时后再次发起注册仍然失败,开始定时发起注册,间隔为1分钟", parentPlatform.getServerGBId());
+                                                // 添加注册任务
+                                                dynamicTask.startCron(registerTaskKey,
+                                                        // 注册失败(注册成功时由程序直接调用了online方法)
+                                                        ()->logger.info("[国标级联] {},平台离线后持续发起注册,失败", parentPlatform.getServerGBId()),
+                                                        60*1000);
+                                            }, null);
+                                        } catch (InvalidArgumentException | ParseException | SipException e) {
+                                            logger.error("[命令发送失败] 国标级联 注册: {}", e.getMessage());
+                                        }
+                                    }
 
-                        }else {
-                            logger.warn("[国标级联]发送心跳收到错误,code: {}, msg: {}", eventResult.statusCode, eventResult.msg);
-                        }
+                                }else {
+                                    logger.warn("[国标级联]发送心跳收到错误,code: {}, msg: {}", eventResult.statusCode, eventResult.msg);
+                                }
 
-                    }, eventResult -> {
-                        // 心跳成功
-                        // 清空之前的心跳超时计数
-                        ParentPlatformCatch platformCatch = redisCatchStorage.queryPlatformCatchInfo(parentPlatform.getServerGBId());
-                        if (platformCatch.getKeepAliveReply() > 0) {
-                            platformCatch.setKeepAliveReply(0);
-                            redisCatchStorage.updatePlatformCatchInfo(platformCatch);
+                            }, eventResult -> {
+                                // 心跳成功
+                                // 清空之前的心跳超时计数
+                                ParentPlatformCatch platformCatch = redisCatchStorage.queryPlatformCatchInfo(parentPlatform.getServerGBId());
+                                if (platformCatch.getKeepAliveReply() > 0) {
+                                    platformCatch.setKeepAliveReply(0);
+                                    redisCatchStorage.updatePlatformCatchInfo(platformCatch);
+                                }
+                            });
+                        } catch (SipException | InvalidArgumentException | ParseException e) {
+                            logger.error("[命令发送失败] 国标级联 发送心跳: {}", e.getMessage());
                         }
-                    }),
+                    },
                     (parentPlatform.getKeepTimeout() - 10)*1000);
         }
     }
@@ -225,14 +248,18 @@ public class PlatformServiceImpl implements IPlatformService {
     @Override
     public void login(ParentPlatform parentPlatform) {
         final String registerTaskKey = REGISTER_KEY_PREFIX + parentPlatform.getServerGBId();
-        commanderForPlatform.register(parentPlatform, eventResult1 -> {
-            logger.info("[国标级联] {},开始定时发起注册,间隔为1分钟", parentPlatform.getServerGBId());
-            // 添加注册任务
-            dynamicTask.startCron(registerTaskKey,
-                    // 注册失败(注册成功时由程序直接调用了online方法)
-                    ()->logger.info("[国标级联] {},平台离线后持续发起注册,失败", parentPlatform.getServerGBId()),
-                    60*1000);
-        }, null);
+        try {
+            commanderForPlatform.register(parentPlatform, eventResult1 -> {
+                logger.info("[国标级联] {},开始定时发起注册,间隔为1分钟", parentPlatform.getServerGBId());
+                // 添加注册任务
+                dynamicTask.startCron(registerTaskKey,
+                        // 注册失败(注册成功时由程序直接调用了online方法)
+                        ()->logger.info("[国标级联] {},平台离线后持续发起注册,失败", parentPlatform.getServerGBId()),
+                        60*1000);
+            }, null);
+        } catch (InvalidArgumentException | ParseException | SipException e) {
+            logger.error("[命令发送失败] 国标级联注册: {}", e.getMessage());
+        }
     }
 
     @Override
@@ -259,7 +286,12 @@ public class PlatformServiceImpl implements IPlatformService {
                         continue;
                     }
                     // 发送GPS消息
-                    commanderForPlatform.sendNotifyMobilePosition(platform, gpsMsgInfo, subscribe);
+                    try {
+                        commanderForPlatform.sendNotifyMobilePosition(platform, gpsMsgInfo, subscribe);
+                    } catch (InvalidArgumentException | ParseException | NoSuchFieldException | SipException |
+                             IllegalAccessException e) {
+                        logger.error("[命令发送失败] 国标级联 移动位置通知: {}", e.getMessage());
+                    }
                 }
             }
         }

+ 209 - 159
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java

@@ -2,11 +2,17 @@ package com.genersoft.iot.vmp.service.impl;
 
 import java.math.BigDecimal;
 import java.math.RoundingMode;
+import java.text.ParseException;
 import java.util.*;
 
+import javax.sip.InvalidArgumentException;
 import javax.sip.ResponseEvent;
+import javax.sip.SipException;
 
 import com.genersoft.iot.vmp.conf.exception.ControllerException;
+import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
+import com.genersoft.iot.vmp.gb28181.bean.*;
+import com.genersoft.iot.vmp.service.IDeviceService;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -22,13 +28,6 @@ import com.alibaba.fastjson.JSONObject;
 import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.conf.DynamicTask;
 import com.genersoft.iot.vmp.conf.UserSetting;
-import com.genersoft.iot.vmp.gb28181.bean.Device;
-import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
-import com.genersoft.iot.vmp.gb28181.bean.InviteStreamCallback;
-import com.genersoft.iot.vmp.gb28181.bean.InviteStreamInfo;
-import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
-import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
-import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
@@ -92,6 +91,10 @@ public class PlayServiceImpl implements IPlayService {
     @Autowired
     private VideoStreamSessionManager streamSession;
 
+
+    @Autowired
+    private IDeviceService deviceService;
+
     @Autowired
     private UserSetting userSetting;
 
@@ -261,14 +264,14 @@ public class PlayServiceImpl implements IPlayService {
         System.out.println("设置超时任务: " + timeOutTaskKey);
         dynamicTask.startDelay( timeOutTaskKey,()->{
 
-            SIPDialog dialog = streamSession.getDialogByStream(device.getDeviceId(), channelId, finalSsrcInfo.getStream());
-            if (dialog != null) {
-                logger.info("[点播超时] 收流超时 deviceId: {}, channelId: {},端口:{}, SSRC: {}", device.getDeviceId(), channelId, finalSsrcInfo.getPort(), finalSsrcInfo.getSsrc());
-                timeoutCallback.run(1, "收流超时");
-                // 点播超时回复BYE 同时释放ssrc以及此次点播的资源
-                cmder.streamByeCmd(device.getDeviceId(), channelId, finalSsrcInfo.getStream(), null);
-            }else {
-                logger.info("[点播超时] 消息未响应 deviceId: {}, channelId: {},端口:{}, SSRC: {}", device.getDeviceId(), channelId, finalSsrcInfo.getPort(), finalSsrcInfo.getSsrc());
+            logger.info("[点播超时] 收流超时 deviceId: {}, channelId: {},端口:{}, SSRC: {}", device.getDeviceId(), channelId, finalSsrcInfo.getPort(), finalSsrcInfo.getSsrc());
+            timeoutCallback.run(1, "收流超时");
+            // 点播超时回复BYE 同时释放ssrc以及此次点播的资源
+            try {
+                cmder.streamByeCmd(device, channelId, finalSsrcInfo.getStream(), null);
+            } catch (InvalidArgumentException | ParseException | SipException e) {
+                logger.error("[点播超时], 发送BYE失败 {}", e.getMessage());
+            } catch (SsrcTransactionNotFoundException e) {
                 timeoutCallback.run(0, "点播超时");
                 mediaServerService.releaseSsrc(mediaServerItem.getId(), finalSsrcInfo.getSsrc());
                 mediaServerService.closeRTPServer(mediaServerItem, finalSsrcInfo.getStream());
@@ -282,73 +285,87 @@ public class PlayServiceImpl implements IPlayService {
             logger.info("[点播端口分配异常],deviceId={},channelId={},ssrcInfo={}", device.getDeviceId(), channelId, ssrcInfo);
             return;
         }
-        cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInuse, JSONObject response) -> {
-            logger.info("收到订阅消息: " + response.toJSONString());
-            System.out.println("停止超时任务: " + timeOutTaskKey);
-            dynamicTask.stop(timeOutTaskKey);
-            // hook响应
-            onPublishHandlerForPlay(mediaServerItemInuse, response, device.getDeviceId(), channelId, uuid);
-            hookEvent.response(mediaServerItemInuse, response);
-            logger.info("[点播成功] deviceId: {}, channelId: {}", device.getDeviceId(), channelId);
-
-        }, (event) -> {
-            ResponseEvent responseEvent = (ResponseEvent)event.event;
-            String contentString = new String(responseEvent.getResponse().getRawContent());
-            // 获取ssrc
-            int ssrcIndex = contentString.indexOf("y=");
-            // 检查是否有y字段
-            if (ssrcIndex >= 0) {
-                //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
-                String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
-                // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
-                if (ssrc.equals(ssrcInResponse)) {
-                    return;
-                }
-                logger.info("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse );
-                if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
-                    logger.info("[点播消息] SSRC修正 {}->{}", ssrc, ssrcInResponse);
-
-                    if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) {
-                        // ssrc 不可用
-                        // 释放ssrc
-                        mediaServerService.releaseSsrc(mediaServerItem.getId(), finalSsrcInfo.getSsrc());
-                        streamSession.remove(device.getDeviceId(), channelId, finalSsrcInfo.getStream());
-                        event.msg = "下级自定义了ssrc,但是此ssrc不可用";
-                        event.statusCode = 400;
-                        errorEvent.response(event);
+        try {
+            cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInuse, JSONObject response) -> {
+                logger.info("收到订阅消息: " + response.toJSONString());
+                System.out.println("停止超时任务: " + timeOutTaskKey);
+                dynamicTask.stop(timeOutTaskKey);
+                // hook响应
+                onPublishHandlerForPlay(mediaServerItemInuse, response, device.getDeviceId(), channelId, uuid);
+                hookEvent.response(mediaServerItemInuse, response);
+                logger.info("[点播成功] deviceId: {}, channelId: {}", device.getDeviceId(), channelId);
+
+            }, (event) -> {
+                ResponseEvent responseEvent = (ResponseEvent)event.event;
+                String contentString = new String(responseEvent.getResponse().getRawContent());
+                // 获取ssrc
+                int ssrcIndex = contentString.indexOf("y=");
+                // 检查是否有y字段
+                if (ssrcIndex >= 0) {
+                    //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
+                    String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
+                    // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
+                    if (ssrc.equals(ssrcInResponse)) {
                         return;
                     }
+                    logger.info("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse );
+                    if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
+                        logger.info("[点播消息] SSRC修正 {}->{}", ssrc, ssrcInResponse);
+
+                        if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) {
+                            // ssrc 不可用
+                            // 释放ssrc
+                            mediaServerService.releaseSsrc(mediaServerItem.getId(), finalSsrcInfo.getSsrc());
+                            streamSession.remove(device.getDeviceId(), channelId, finalSsrcInfo.getStream());
+                            event.msg = "下级自定义了ssrc,但是此ssrc不可用";
+                            event.statusCode = 400;
+                            errorEvent.response(event);
+                            return;
+                        }
 
-                    // 单端口模式streamId也有变化,需要重新设置监听
-                    if (!mediaServerItem.isRtpEnable()) {
-                        // 添加订阅
-                        HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId());
-                        subscribe.removeSubscribe(hookSubscribe);
-                        hookSubscribe.getContent().put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase());
-                        subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject response)->{
-                                    logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString());
-                                    dynamicTask.stop(timeOutTaskKey);
-                                    // hook响应
-                                    onPublishHandlerForPlay(mediaServerItemInUse, response, device.getDeviceId(), channelId, uuid);
-                                    hookEvent.response(mediaServerItemInUse, response);
-                                });
-                    }
-                    // 关闭rtp server
-                    mediaServerService.closeRTPServer(mediaServerItem, finalSsrcInfo.getStream());
-                    // 重新开启ssrc server
-                    mediaServerService.openRTPServer(mediaServerItem, finalSsrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), false, finalSsrcInfo.getPort());
+                        // 单端口模式streamId也有变化,需要重新设置监听
+                        if (!mediaServerItem.isRtpEnable()) {
+                            // 添加订阅
+                            HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId());
+                            subscribe.removeSubscribe(hookSubscribe);
+                            hookSubscribe.getContent().put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase());
+                            subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject response)->{
+                                        logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString());
+                                        dynamicTask.stop(timeOutTaskKey);
+                                        // hook响应
+                                        onPublishHandlerForPlay(mediaServerItemInUse, response, device.getDeviceId(), channelId, uuid);
+                                        hookEvent.response(mediaServerItemInUse, response);
+                                    });
+                        }
+                        // 关闭rtp server
+                        mediaServerService.closeRTPServer(mediaServerItem, finalSsrcInfo.getStream());
+                        // 重新开启ssrc server
+                        mediaServerService.openRTPServer(mediaServerItem, finalSsrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), false, finalSsrcInfo.getPort());
 
+                    }
                 }
-            }
-        }, (event) -> {
+            }, (event) -> {
+                dynamicTask.stop(timeOutTaskKey);
+                mediaServerService.closeRTPServer(mediaServerItem, finalSsrcInfo.getStream());
+                // 释放ssrc
+                mediaServerService.releaseSsrc(mediaServerItem.getId(), finalSsrcInfo.getSsrc());
+
+                streamSession.remove(device.getDeviceId(), channelId, finalSsrcInfo.getStream());
+                errorEvent.response(event);
+            });
+        } catch (InvalidArgumentException | SipException | ParseException e) {
+
+            logger.error("[命令发送失败] 点播消息: {}", e.getMessage());
             dynamicTask.stop(timeOutTaskKey);
             mediaServerService.closeRTPServer(mediaServerItem, finalSsrcInfo.getStream());
             // 释放ssrc
             mediaServerService.releaseSsrc(mediaServerItem.getId(), finalSsrcInfo.getSsrc());
 
             streamSession.remove(device.getDeviceId(), channelId, finalSsrcInfo.getStream());
-            errorEvent.response(event);
-        });
+            SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(new CmdSendFailEvent(null));
+            eventResult.msg = "命令发送失败";
+            errorEvent.response(eventResult);
+        }
     }
 
     @Override
@@ -439,17 +456,18 @@ public class PlayServiceImpl implements IPlayService {
             playBackResult.setCode(ErrorCode.ERROR100.getCode());
             playBackResult.setMsg("回放超时");
             playBackResult.setData(requestMessage);
-            SIPDialog dialog = streamSession.getDialogByStream(deviceId, channelId, ssrcInfo.getStream());
-            // 点播超时回复BYE 同时释放ssrc以及此次点播的资源
-            if (dialog != null) {
+
+            try {
+                cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null);
+            } catch (InvalidArgumentException | ParseException | SipException e) {
+                logger.error("[录像流]回放超时 发送BYE失败 {}", e.getMessage());
+            } catch (SsrcTransactionNotFoundException e) {
                 // 点播超时回复BYE 同时释放ssrc以及此次点播的资源
-                cmder.streamByeCmd(device.getDeviceId(), channelId, ssrcInfo.getStream(), null);
-            }else {
                 mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
                 mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
                 streamSession.remove(deviceId, channelId, ssrcInfo.getStream());
             }
-            cmder.streamByeCmd(device.getDeviceId(), channelId, ssrcInfo.getStream(), null);
+
             // 回复之前所有的点播请求
             playBackCallback.call(playBackResult);
             result.setResult(WVPResult.fail(ErrorCode.ERROR100.getCode(), "回放超时"));
@@ -489,59 +507,67 @@ public class PlayServiceImpl implements IPlayService {
             playBackCallback.call(playBackResult);
         };
 
-        cmder.playbackStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, infoCallBack,
-                hookEvent, eventResult -> {
-                    if (eventResult.type == SipSubscribe.EventResultType.response) {
-                        ResponseEvent responseEvent = (ResponseEvent)eventResult.event;
-                        String contentString = new String(responseEvent.getResponse().getRawContent());
-                        // 获取ssrc
-                        int ssrcIndex = contentString.indexOf("y=");
-                        // 检查是否有y字段
-                        if (ssrcIndex >= 0) {
-                            //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
-                            String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
-                            // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
-                            if (ssrcInfo.getSsrc().equals(ssrcInResponse)) {
-                                return;
-                            }
-                            logger.info("[回放消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse );
-                            if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
-                                logger.info("[回放消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
-
-                                if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) {
-                                    // ssrc 不可用
-                                    // 释放ssrc
-                                    mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
-                                    streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
-                                    eventResult.msg = "下级自定义了ssrc,但是此ssrc不可用";
-                                    eventResult.statusCode = 400;
-                                    errorEvent.response(eventResult);
+        try {
+            cmder.playbackStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, infoCallBack,
+                    hookEvent, eventResult -> {
+                        if (eventResult.type == SipSubscribe.EventResultType.response) {
+                            ResponseEvent responseEvent = (ResponseEvent)eventResult.event;
+                            String contentString = new String(responseEvent.getResponse().getRawContent());
+                            // 获取ssrc
+                            int ssrcIndex = contentString.indexOf("y=");
+                            // 检查是否有y字段
+                            if (ssrcIndex >= 0) {
+                                //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
+                                String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
+                                // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
+                                if (ssrcInfo.getSsrc().equals(ssrcInResponse)) {
                                     return;
                                 }
-
-                                // 单端口模式streamId也有变化,需要重新设置监听
-                                if (!mediaServerItem.isRtpEnable()) {
-                                    // 添加订阅
-                                    HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
-                                    subscribe.removeSubscribe(hookSubscribe);
-                                    hookSubscribe.getContent().put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase());
-                                    subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject response)->{
-                                        logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString());
-                                        dynamicTask.stop(playBackTimeOutTaskKey);
-                                        // hook响应
-                                        onPublishHandlerForPlay(mediaServerItemInUse, response, device.getDeviceId(), channelId, uuid);
-                                        hookEvent.call(new InviteStreamInfo(mediaServerItem, null, eventResult.callId, "rtp", ssrcInfo.getStream()));
-                                    });
+                                logger.info("[回放消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse );
+                                if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
+                                    logger.info("[回放消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
+
+                                    if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) {
+                                        // ssrc 不可用
+                                        // 释放ssrc
+                                        mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
+                                        streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
+                                        eventResult.msg = "下级自定义了ssrc,但是此ssrc不可用";
+                                        eventResult.statusCode = 400;
+                                        errorEvent.response(eventResult);
+                                        return;
+                                    }
+
+                                    // 单端口模式streamId也有变化,需要重新设置监听
+                                    if (!mediaServerItem.isRtpEnable()) {
+                                        // 添加订阅
+                                        HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
+                                        subscribe.removeSubscribe(hookSubscribe);
+                                        hookSubscribe.getContent().put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase());
+                                        subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject response)->{
+                                            logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString());
+                                            dynamicTask.stop(playBackTimeOutTaskKey);
+                                            // hook响应
+                                            onPublishHandlerForPlay(mediaServerItemInUse, response, device.getDeviceId(), channelId, uuid);
+                                            hookEvent.call(new InviteStreamInfo(mediaServerItem, null, eventResult.callId, "rtp", ssrcInfo.getStream()));
+                                        });
+                                    }
+                                    // 关闭rtp server
+                                    mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
+                                    // 重新开启ssrc server
+                                    mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), true, ssrcInfo.getPort());
                                 }
-                                // 关闭rtp server
-                                mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
-                                // 重新开启ssrc server
-                                mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), true, ssrcInfo.getPort());
                             }
                         }
-                    }
 
-                }, errorEvent);
+                    }, errorEvent);
+        } catch (InvalidArgumentException | SipException | ParseException e) {
+            logger.error("[命令发送失败] 回放: {}", e.getMessage());
+
+            SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(new CmdSendFailEvent(null));
+            eventResult.msg = "命令发送失败";
+            errorEvent.response(eventResult);
+        }
         return result;
     }
 
@@ -587,46 +613,57 @@ public class PlayServiceImpl implements IPlayService {
             downloadResult.setCode(ErrorCode.ERROR100.getCode());
             downloadResult.setMsg("录像下载请求超时");
             hookCallBack.call(downloadResult);
-            SIPDialog dialog = streamSession.getDialogByStream(deviceId, channelId, ssrcInfo.getStream());
+
             // 点播超时回复BYE 同时释放ssrc以及此次点播的资源
-            if (dialog != null) {
-                // 点播超时回复BYE 同时释放ssrc以及此次点播的资源
-                cmder.streamByeCmd(device.getDeviceId(), channelId, ssrcInfo.getStream(), null);
-            }else {
+            try {
+                cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null);
+            } catch (InvalidArgumentException | ParseException | SipException e) {
+                logger.error("[录像流]录像下载请求超时, 发送BYE失败 {}", e.getMessage());
+            } catch (SsrcTransactionNotFoundException e) {
                 mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
                 mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
                 streamSession.remove(deviceId, channelId, ssrcInfo.getStream());
             }
-            cmder.streamByeCmd(device.getDeviceId(), channelId, ssrcInfo.getStream(), null);
             // 回复之前所有的点播请求
             hookCallBack.call(downloadResult);
         }, userSetting.getPlayTimeout());
-        cmder.downloadStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, downloadSpeed, infoCallBack,
-                inviteStreamInfo -> {
-                    logger.info("收到订阅消息: " + inviteStreamInfo.getResponse().toJSONString());
-                    dynamicTask.stop(downLoadTimeOutTaskKey);
-                    StreamInfo streamInfo = onPublishHandler(inviteStreamInfo.getMediaServerItem(), inviteStreamInfo.getResponse(), deviceId, channelId);
-                    streamInfo.setStartTime(startTime);
-                    streamInfo.setEndTime(endTime);
-                    redisCatchStorage.startDownload(streamInfo, inviteStreamInfo.getCallId());
-                    wvpResult.setCode(ErrorCode.SUCCESS.getCode());
-                    wvpResult.setMsg(ErrorCode.SUCCESS.getMsg());
-                    wvpResult.setData(streamInfo);
-                    downloadResult.setCode(ErrorCode.SUCCESS.getCode());
-                    downloadResult.setMsg(ErrorCode.SUCCESS.getMsg());
-                    downloadResult.setMediaServerItem(inviteStreamInfo.getMediaServerItem());
-                    downloadResult.setResponse(inviteStreamInfo.getResponse());
-                    hookCallBack.call(downloadResult);
-                }, event -> {
-                    dynamicTask.stop(downLoadTimeOutTaskKey);
-                    downloadResult.setCode(ErrorCode.ERROR100.getCode());
-                    downloadResult.setMsg(String.format("录像下载失败, 错误码: %s, %s", event.statusCode, event.msg));
-                    wvpResult.setCode(ErrorCode.ERROR100.getCode());
-                    wvpResult.setMsg(String.format("录像下载失败, 错误码: %s, %s", event.statusCode, event.msg));
-                    downloadResult.setEvent(event);
-                    hookCallBack.call(downloadResult);
-                    streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
-                });
+
+        SipSubscribe.Event errorEvent = event -> {
+            dynamicTask.stop(downLoadTimeOutTaskKey);
+            downloadResult.setCode(ErrorCode.ERROR100.getCode());
+            downloadResult.setMsg(String.format("录像下载失败, 错误码: %s, %s", event.statusCode, event.msg));
+            wvpResult.setCode(ErrorCode.ERROR100.getCode());
+            wvpResult.setMsg(String.format("录像下载失败, 错误码: %s, %s", event.statusCode, event.msg));
+            downloadResult.setEvent(event);
+            hookCallBack.call(downloadResult);
+            streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
+        };
+
+        try {
+            cmder.downloadStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, downloadSpeed, infoCallBack,
+                    inviteStreamInfo -> {
+                        logger.info("收到订阅消息: " + inviteStreamInfo.getResponse().toJSONString());
+                        dynamicTask.stop(downLoadTimeOutTaskKey);
+                        StreamInfo streamInfo = onPublishHandler(inviteStreamInfo.getMediaServerItem(), inviteStreamInfo.getResponse(), deviceId, channelId);
+                        streamInfo.setStartTime(startTime);
+                        streamInfo.setEndTime(endTime);
+                        redisCatchStorage.startDownload(streamInfo, inviteStreamInfo.getCallId());
+                        wvpResult.setCode(ErrorCode.SUCCESS.getCode());
+                        wvpResult.setMsg(ErrorCode.SUCCESS.getMsg());
+                        wvpResult.setData(streamInfo);
+                        downloadResult.setCode(ErrorCode.SUCCESS.getCode());
+                        downloadResult.setMsg(ErrorCode.SUCCESS.getMsg());
+                        downloadResult.setMediaServerItem(inviteStreamInfo.getMediaServerItem());
+                        downloadResult.setResponse(inviteStreamInfo.getResponse());
+                        hookCallBack.call(downloadResult);
+                    }, errorEvent);
+        } catch (InvalidArgumentException | SipException | ParseException e) {
+            logger.error("[命令发送失败] 录像下载: {}", e.getMessage());
+
+            SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(new CmdSendFailEvent(null));
+            eventResult.msg = "命令发送失败";
+            errorEvent.response(eventResult);
+        }
         return result;
     }
 
@@ -705,7 +742,11 @@ public class PlayServiceImpl implements IPlayService {
             for (SendRtpItem sendRtpItem : sendRtpItems) {
                 if (sendRtpItem.getMediaServerId().equals(mediaServerId)) {
                     ParentPlatform platform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
-                    sipCommanderFroPlatform.streamByeCmd(platform, sendRtpItem.getCallId());
+                    try {
+                        sipCommanderFroPlatform.streamByeCmd(platform, sendRtpItem.getCallId());
+                    } catch (SipException | InvalidArgumentException | ParseException e) {
+                        logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
+                    }
                 }
             }
         }
@@ -714,8 +755,17 @@ public class PlayServiceImpl implements IPlayService {
         if (allSsrc.size() > 0) {
             for (SsrcTransaction ssrcTransaction : allSsrc) {
                 if(ssrcTransaction.getMediaServerId().equals(mediaServerId)) {
-                    cmder.streamByeCmd(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(),
-                            ssrcTransaction.getStream(), null);
+                    Device device = deviceService.queryDevice(ssrcTransaction.getDeviceId());
+                    if (device == null) {
+                        continue;
+                    }
+                    try {
+                        cmder.streamByeCmd(device, ssrcTransaction.getChannelId(),
+                                ssrcTransaction.getStream(), null);
+                    } catch (InvalidArgumentException | ParseException | SipException |
+                             SsrcTransactionNotFoundException e) {
+                        logger.error("[zlm离线]为正在使用此zlm的设备, 发送BYE失败 {}", e.getMessage());
+                    }
                 }
             }
         }

+ 18 - 4
src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisAlarmMsgListener.java

@@ -16,7 +16,9 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
 
-import javax.validation.constraints.NotNull;
+import javax.sip.InvalidArgumentException;
+import javax.sip.SipException;
+import java.text.ParseException;
 import java.util.List;
 import java.util.concurrent.ConcurrentLinkedQueue;
 
@@ -78,16 +80,28 @@ public class RedisAlarmMsgListener implements MessageListener {
                         List<ParentPlatform> parentPlatforms = storage.queryEnableParentPlatformList(true);
                         if (parentPlatforms.size() > 0) {
                             for (ParentPlatform parentPlatform : parentPlatforms) {
-                                commanderForPlatform.sendAlarmMessage(parentPlatform, deviceAlarm);
+                                try {
+                                    commanderForPlatform.sendAlarmMessage(parentPlatform, deviceAlarm);
+                                } catch (SipException | InvalidArgumentException | ParseException e) {
+                                    logger.error("[命令发送失败] 国标级联 发送报警: {}", e.getMessage());
+                                }
                             }
                         }
                     }else {
                         Device device = storage.queryVideoDevice(gbId);
                         ParentPlatform platform = storage.queryParentPlatByServerGBId(gbId);
                         if (device != null && platform == null) {
-                            commander.sendAlarmMessage(device, deviceAlarm);
+                            try {
+                                commander.sendAlarmMessage(device, deviceAlarm);
+                            } catch (InvalidArgumentException | SipException | ParseException e) {
+                                logger.error("[命令发送失败] 发送报警: {}", e.getMessage());
+                            }
                         }else if (device == null && platform != null){
-                            commanderForPlatform.sendAlarmMessage(platform, deviceAlarm);
+                            try {
+                                commanderForPlatform.sendAlarmMessage(platform, deviceAlarm);
+                            } catch (InvalidArgumentException | SipException | ParseException e) {
+                                logger.error("[命令发送失败] 发送报警: {}", e.getMessage());
+                            }
                         }else {
                             logger.warn("无法确定" + gbId + "是平台还是设备");
                         }

+ 16 - 7
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/MobilePosition/MobilePositionController.java

@@ -1,5 +1,6 @@
 package com.genersoft.iot.vmp.vmanager.gb28181.MobilePosition;
 
+import java.text.ParseException;
 import java.util.List;
 import java.util.UUID;
 
@@ -31,6 +32,9 @@ import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.context.request.async.DeferredResult;
 
+import javax.sip.InvalidArgumentException;
+import javax.sip.SipException;
+
 /**
  *  位置信息管理
  */
@@ -105,13 +109,18 @@ public class MobilePositionController {
         Device device = storager.queryVideoDevice(deviceId);
         String uuid = UUID.randomUUID().toString();
         String key = DeferredResultHolder.CALLBACK_CMD_MOBILEPOSITION + deviceId;
-        cmder.mobilePostitionQuery(device, event -> {
-			RequestMessage msg = new RequestMessage();
-			msg.setId(uuid);
-            msg.setKey(key);
-			msg.setData(String.format("获取移动位置信息失败,错误码: %s, %s", event.statusCode, event.msg));
-			resultHolder.invokeResult(msg);
-		});
+        try {
+            cmder.mobilePostitionQuery(device, event -> {
+                RequestMessage msg = new RequestMessage();
+                msg.setId(uuid);
+                msg.setKey(key);
+                msg.setData(String.format("获取移动位置信息失败,错误码: %s, %s", event.statusCode, event.msg));
+                resultHolder.invokeResult(msg);
+            });
+        } catch (InvalidArgumentException | SipException | ParseException e) {
+            logger.error("[命令发送失败] 获取移动位置信息: {}", e.getMessage());
+            throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
+        }
         DeferredResult<MobilePosition> result = new DeferredResult<MobilePosition>(5*1000L);
 		result.onTimeout(()->{
 			logger.warn(String.format("获取移动位置信息超时"));

+ 20 - 2
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/alarm/AlarmController.java

@@ -6,6 +6,7 @@ import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
+import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookListener;
 import com.genersoft.iot.vmp.service.IDeviceAlarmService;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.utils.DateUtil;
@@ -16,6 +17,8 @@ import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.responses.ApiResponse;
 import io.swagger.v3.oas.annotations.tags.Tag;
+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;
@@ -23,6 +26,9 @@ import org.springframework.util.ObjectUtils;
 import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.*;
 
+import javax.sip.InvalidArgumentException;
+import javax.sip.SipException;
+import java.text.ParseException;
 import java.time.LocalDateTime;
 import java.util.Arrays;
 import java.util.List;
@@ -33,6 +39,8 @@ import java.util.List;
 @RequestMapping("/api/alarm")
 public class AlarmController {
 
+    private final static Logger logger = LoggerFactory.getLogger(AlarmController.class);
+
     @Autowired
     private IDeviceAlarmService deviceAlarmService;
 
@@ -108,9 +116,19 @@ public class AlarmController {
         deviceAlarm.setLatitude(39.33333);
 
         if (device != null && platform == null) {
-            commander.sendAlarmMessage(device, deviceAlarm);
+
+            try {
+                commander.sendAlarmMessage(device, deviceAlarm);
+            } catch (InvalidArgumentException | SipException | ParseException e) {
+
+            }
         }else if (device == null && platform != null){
-            commanderForPlatform.sendAlarmMessage(platform, deviceAlarm);
+            try {
+                commanderForPlatform.sendAlarmMessage(platform, deviceAlarm);
+            } catch (SipException | InvalidArgumentException | ParseException e) {
+                logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
+                throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " +  e.getMessage());
+            }
         }else {
             throw new ControllerException(ErrorCode.ERROR100.getCode(),"无法确定" + deviceId + "是平台还是设备");
         }

+ 31 - 16
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceConfig.java

@@ -8,12 +8,14 @@
 package com.genersoft.iot.vmp.vmanager.gb28181.device;
 
 import com.alibaba.fastjson.JSONObject;
+import com.genersoft.iot.vmp.conf.exception.ControllerException;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 
+import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -26,6 +28,9 @@ import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.context.request.async.DeferredResult;
 
+import javax.sip.InvalidArgumentException;
+import javax.sip.SipException;
+import java.text.ParseException;
 import java.util.UUID;
 
 @Tag(name = "国标设备配置")
@@ -75,14 +80,19 @@ public class DeviceConfig {
 		Device device = storager.queryVideoDevice(deviceId);
 		String uuid = UUID.randomUUID().toString();
 		String key = DeferredResultHolder.CALLBACK_CMD_DEVICECONFIG + deviceId + channelId;
-		cmder.deviceBasicConfigCmd(device, channelId, name, expiration, heartBeatInterval, heartBeatCount, event -> {
-			RequestMessage msg = new RequestMessage();
-			msg.setId(uuid);
-			msg.setKey(key);
-			msg.setData(String.format("设备配置操作失败,错误码: %s, %s", event.statusCode, event.msg));
-			resultHolder.invokeResult(msg);
-		});
-        DeferredResult<String> result = new DeferredResult<String>(3 * 1000L);
+		try {
+			cmder.deviceBasicConfigCmd(device, channelId, name, expiration, heartBeatInterval, heartBeatCount, event -> {
+				RequestMessage msg = new RequestMessage();
+				msg.setId(uuid);
+				msg.setKey(key);
+				msg.setData(String.format("设备配置操作失败,错误码: %s, %s", event.statusCode, event.msg));
+				resultHolder.invokeResult(msg);
+			});
+		} catch (InvalidArgumentException | SipException | ParseException e) {
+			logger.error("[命令发送失败] 设备配置: {}", e.getMessage());
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
+		}
+		DeferredResult<String> result = new DeferredResult<String>(3 * 1000L);
 		result.onTimeout(() -> {
 			logger.warn(String.format("设备配置操作超时, 设备未返回应答指令"));
 			// 释放rtpserver
@@ -121,14 +131,19 @@ public class DeviceConfig {
 		String key = DeferredResultHolder.CALLBACK_CMD_CONFIGDOWNLOAD + (ObjectUtils.isEmpty(channelId) ? deviceId : channelId);
 		String uuid = UUID.randomUUID().toString();
 		Device device = storager.queryVideoDevice(deviceId);
-		cmder.deviceConfigQuery(device, channelId, configType, event -> {
-			RequestMessage msg = new RequestMessage();
-			msg.setId(uuid);
-			msg.setKey(key);
-			msg.setData(String.format("获取设备配置失败,错误码: %s, %s", event.statusCode, event.msg));
-			resultHolder.invokeResult(msg);
-		});
-        DeferredResult<String> result = new DeferredResult<String > (3 * 1000L);
+		try {
+			cmder.deviceConfigQuery(device, channelId, configType, event -> {
+				RequestMessage msg = new RequestMessage();
+				msg.setId(uuid);
+				msg.setKey(key);
+				msg.setData(String.format("获取设备配置失败,错误码: %s, %s", event.statusCode, event.msg));
+				resultHolder.invokeResult(msg);
+			});
+		} catch (InvalidArgumentException | SipException | ParseException e) {
+			logger.error("[命令发送失败] 获取设备配置: {}", e.getMessage());
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
+		}
+		DeferredResult<String> result = new DeferredResult<String > (3 * 1000L);
 		result.onTimeout(()->{
 			logger.warn(String.format("获取设备配置超时"));
 			// 释放rtpserver

+ 88 - 55
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceControl.java

@@ -29,6 +29,9 @@ import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.context.request.async.DeferredResult;
 
+import javax.sip.InvalidArgumentException;
+import javax.sip.SipException;
+import java.text.ParseException;
 import java.util.UUID;
 
 @Tag(name  = "国标设备控制")
@@ -61,10 +64,12 @@ public class DeviceControl {
             logger.debug("设备远程启动API调用");
         }
         Device device = storager.queryVideoDevice(deviceId);
-        if (!cmder.teleBootCmd(device)) {
-			logger.warn("设备远程启动API调用失败!");
-            throw new ControllerException(ErrorCode.ERROR100);
-        }
+		try {
+			cmder.teleBootCmd(device);
+		} catch (InvalidArgumentException | SipException | ParseException e) {
+			logger.error("[命令发送失败] 远程启动: {}", e.getMessage());
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
+		}
     }
 
     /**
@@ -101,13 +106,18 @@ public class DeviceControl {
 			return result;
 		}
 		resultHolder.put(key, uuid, result);
-		cmder.recordCmd(device, channelId, recordCmdStr, event -> {
-            RequestMessage msg = new RequestMessage();
-			msg.setId(uuid);
-			msg.setKey(key);
-			msg.setData(String.format("开始/停止录像操作失败,错误码: %s, %s", event.statusCode, event.msg));
-			resultHolder.invokeAllResult(msg);
-		});
+		try {
+			cmder.recordCmd(device, channelId, recordCmdStr, event -> {
+				RequestMessage msg = new RequestMessage();
+				msg.setId(uuid);
+				msg.setKey(key);
+				msg.setData(String.format("开始/停止录像操作失败,错误码: %s, %s", event.statusCode, event.msg));
+				resultHolder.invokeAllResult(msg);
+			});
+		} catch (InvalidArgumentException | SipException | ParseException e) {
+			logger.error("[命令发送失败] 开始/停止录像: {}", e.getMessage());
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
+		}
 
 		return result;
 	}
@@ -123,21 +133,26 @@ public class DeviceControl {
 	@Parameter(name = "channelId", description = "通道国标编号", required = true)
 	@Parameter(name = "guardCmdStr", description = "命令, 可选值:SetGuard(布防),ResetGuard(撤防)", required = true)
 	@GetMapping("/guard/{deviceId}/{guardCmdStr}")
-	public DeferredResult<ResponseEntity<String>> guardApi(@PathVariable String deviceId, String channelId, @PathVariable String guardCmdStr) {
+	public DeferredResult<String> guardApi(@PathVariable String deviceId, String channelId, @PathVariable String guardCmdStr) {
 		if (logger.isDebugEnabled()) {
 			logger.debug("布防/撤防API调用");
 		}
 		Device device = storager.queryVideoDevice(deviceId);
 		String key = DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + deviceId + channelId;
 		String uuid =UUID.randomUUID().toString();
-		cmder.guardCmd(device, guardCmdStr, event -> {
-			RequestMessage msg = new RequestMessage();
-			msg.setId(uuid);
-			msg.setKey(key);
-			msg.setData(String.format("布防/撤防操作失败,错误码: %s, %s", event.statusCode, event.msg));
-			resultHolder.invokeResult(msg);
-		});
-        DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(3 * 1000L);
+		try {
+			cmder.guardCmd(device, guardCmdStr, event -> {
+				RequestMessage msg = new RequestMessage();
+				msg.setId(uuid);
+				msg.setKey(key);
+				msg.setData(String.format("布防/撤防操作失败,错误码: %s, %s", event.statusCode, event.msg));
+				resultHolder.invokeResult(msg);
+			});
+		} catch (InvalidArgumentException | SipException | ParseException e) {
+			logger.error("[命令发送失败] 布防/撤防操作: {}", e.getMessage());
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送: " + e.getMessage());
+		}
+		DeferredResult<String> result = new DeferredResult<>(3 * 1000L);
 		resultHolder.put(key, uuid, result);
 		result.onTimeout(() -> {
 			logger.warn(String.format("布防/撤防操作超时, 设备未返回应答指令"));
@@ -174,14 +189,19 @@ public class DeviceControl {
 		Device device = storager.queryVideoDevice(deviceId);
 		String uuid = UUID.randomUUID().toString();
 		String key = DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + deviceId + channelId;
-		cmder.alarmCmd(device, alarmMethod, alarmType, event -> {
-			RequestMessage msg = new RequestMessage();
-			msg.setId(uuid);
-			msg.setKey(key);
-			msg.setData(String.format("报警复位操作失败,错误码: %s, %s", event.statusCode, event.msg));
-			resultHolder.invokeResult(msg);
-		});
-        DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(3 * 1000L);
+		try {
+			cmder.alarmCmd(device, alarmMethod, alarmType, event -> {
+				RequestMessage msg = new RequestMessage();
+				msg.setId(uuid);
+				msg.setKey(key);
+				msg.setData(String.format("报警复位操作失败,错误码: %s, %s", event.statusCode, event.msg));
+				resultHolder.invokeResult(msg);
+			});
+		} catch (InvalidArgumentException | SipException | ParseException e) {
+			logger.error("[命令发送失败] 报警复位: {}", e.getMessage());
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
+		}
+		DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(3 * 1000L);
 		result.onTimeout(() -> {
 			logger.warn(String.format("报警复位操作超时, 设备未返回应答指令"));
 			// 释放rtpserver
@@ -205,23 +225,23 @@ public class DeviceControl {
 	@Parameter(name = "deviceId", description = "设备国标编号", required = true)
 	@Parameter(name = "channelId", description = "通道国标编号")
 	@GetMapping("/i_frame/{deviceId}")
-	public ResponseEntity<String> iFrame(@PathVariable String deviceId,
+	public JSONObject iFrame(@PathVariable String deviceId,
 										@RequestParam(required = false) String channelId) {
 		if (logger.isDebugEnabled()) {
 			logger.debug("强制关键帧API调用");
 		}
 		Device device = storager.queryVideoDevice(deviceId);
-		boolean sucsess = cmder.iFrameCmd(device, channelId);
-		if (sucsess) {
-			JSONObject json = new JSONObject();
-			json.put("DeviceID", deviceId);
-			json.put("ChannelID", channelId);
-			json.put("Result", "OK");
-			return new ResponseEntity<>(json.toJSONString(), HttpStatus.OK);
-		} else {
-			logger.warn("强制关键帧API调用失败!");
-			return new ResponseEntity<String>("强制关键帧API调用失败!", HttpStatus.INTERNAL_SERVER_ERROR);
+		try {
+			cmder.iFrameCmd(device, channelId);
+		} catch (InvalidArgumentException | SipException | ParseException e) {
+			logger.error("[命令发送失败] 强制关键帧: {}", e.getMessage());
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
 		}
+		JSONObject json = new JSONObject();
+		json.put("DeviceID", deviceId);
+		json.put("ChannelID", channelId);
+		json.put("Result", "OK");
+		return json;
 	}
 
 	/**
@@ -240,7 +260,7 @@ public class DeviceControl {
 	@Parameter(name = "presetIndex", description = "调用预置位编号")
 	@Parameter(name = "resetTime", description = "自动归位时间间隔")
 	@GetMapping("/home_position/{deviceId}/{enabled}")
-	public DeferredResult<ResponseEntity<String>> homePositionApi(@PathVariable String deviceId,
+	public DeferredResult<String> homePositionApi(@PathVariable String deviceId,
 																@PathVariable String enabled,
 																@RequestParam(required = false) String resetTime,
 																@RequestParam(required = false) String presetIndex,
@@ -251,14 +271,19 @@ public class DeviceControl {
 		String key = DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (ObjectUtils.isEmpty(channelId) ? deviceId : channelId);
 		String uuid = UUID.randomUUID().toString();
 		Device device = storager.queryVideoDevice(deviceId);
-		cmder.homePositionCmd(device, channelId, enabled, resetTime, presetIndex, event -> {
-			RequestMessage msg = new RequestMessage();
-			msg.setId(uuid);
-			msg.setKey(key);
-			msg.setData(String.format("看守位控制操作失败,错误码: %s, %s", event.statusCode, event.msg));
-			resultHolder.invokeResult(msg);
-		});
-        DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(3 * 1000L);
+		try {
+			cmder.homePositionCmd(device, channelId, enabled, resetTime, presetIndex, event -> {
+				RequestMessage msg = new RequestMessage();
+				msg.setId(uuid);
+				msg.setKey(key);
+				msg.setData(String.format("看守位控制操作失败,错误码: %s, %s", event.statusCode, event.msg));
+				resultHolder.invokeResult(msg);
+			});
+		} catch (InvalidArgumentException | SipException | ParseException e) {
+			logger.error("[命令发送失败] 看守位控制: {}", e.getMessage());
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
+		}
+		DeferredResult<String> result = new DeferredResult<>(3 * 1000L);
 		result.onTimeout(() -> {
 			logger.warn(String.format("看守位控制操作超时, 设备未返回应答指令"));
 			// 释放rtpserver
@@ -297,14 +322,14 @@ public class DeviceControl {
 	@Parameter(name = "lengthx", description = "拉框长度像素值", required = true)
 	@Parameter(name = "lengthy", description = "lengthy", required = true)
 	@GetMapping("drag_zoom/zoom_in")
-	public ResponseEntity<String> dragZoomIn(@RequestParam String deviceId,
+	public void dragZoomIn(@RequestParam String deviceId,
 											 @RequestParam(required = false) String channelId,
 											 @RequestParam int length,
 											 @RequestParam int width,
 											 @RequestParam int midpointx,
 											 @RequestParam int midpointy,
 											 @RequestParam int lengthx,
-											 @RequestParam int lengthy){
+											 @RequestParam int lengthy) throws RuntimeException {
 		if (logger.isDebugEnabled()) {
 			logger.debug(String.format("设备拉框放大 API调用,deviceId:%s ,channelId:%s ,length:%d ,width:%d ,midpointx:%d ,midpointy:%d ,lengthx:%d ,lengthy:%d",deviceId, channelId, length, width, midpointx, midpointy,lengthx, lengthy));
 		}
@@ -318,8 +343,12 @@ public class DeviceControl {
 		cmdXml.append("<LengthX>" + lengthx+ "</LengthX>\r\n");
 		cmdXml.append("<LengthY>" + lengthy+ "</LengthY>\r\n");
 		cmdXml.append("</DragZoomIn>\r\n");
-		cmder.dragZoomCmd(device, channelId, cmdXml.toString());
-		return new ResponseEntity<String>("success", HttpStatus.OK);
+		try {
+			cmder.dragZoomCmd(device, channelId, cmdXml.toString());
+		} catch (InvalidArgumentException | SipException | ParseException e) {
+			logger.error("[命令发送失败] 拉框放大: {}", e.getMessage());
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " +  e.getMessage());
+		}
 	}
 
 	/**
@@ -344,7 +373,7 @@ public class DeviceControl {
 	@Parameter(name = "lengthx", description = "拉框长度像素值", required = true)
 	@Parameter(name = "lengthy", description = "拉框宽度像素值", required = true)
 	@GetMapping("/drag_zoom/zoom_out")
-	public ResponseEntity<String> dragZoomOut(@RequestParam String deviceId,
+	public void dragZoomOut(@RequestParam String deviceId,
 											  @RequestParam(required = false) String channelId,
 											  @RequestParam int length,
 											  @RequestParam int width,
@@ -366,7 +395,11 @@ public class DeviceControl {
 		cmdXml.append("<LengthX>" + lengthx+ "</LengthX>\r\n");
 		cmdXml.append("<LengthY>" + lengthy+ "</LengthY>\r\n");
 		cmdXml.append("</DragZoomOut>\r\n");
-		cmder.dragZoomCmd(device, channelId, cmdXml.toString());
-		return new ResponseEntity<String>("success",HttpStatus.OK);
+		try {
+			cmder.dragZoomCmd(device, channelId, cmdXml.toString());
+		} catch (InvalidArgumentException | SipException | ParseException e) {
+			logger.error("[命令发送失败] 拉框缩小: {}", e.getMessage());
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " +  e.getMessage());
+		}
 	}
 }

+ 28 - 15
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java

@@ -39,8 +39,11 @@ import org.springframework.web.context.request.async.DeferredResult;
 
 import javax.servlet.http.HttpServletResponse;
 import javax.sip.DialogState;
+import javax.sip.InvalidArgumentException;
+import javax.sip.SipException;
 import java.io.*;
 import java.nio.file.Files;
+import java.text.ParseException;
 import java.util.*;
 
 @Tag(name  = "国标设备查询", description = "国标设备查询")
@@ -315,13 +318,18 @@ public class DeviceQuery {
 			result.setResult(new ResponseEntity(String.format("设备%s不存在", deviceId),HttpStatus.OK));
 			return result;
 		}
-		cmder.deviceStatusQuery(device, event -> {
-			RequestMessage msg = new RequestMessage();
-			msg.setId(uuid);
-			msg.setKey(key);
-			msg.setData(String.format("获取设备状态失败,错误码: %s, %s", event.statusCode, event.msg));
-			resultHolder.invokeResult(msg);
-		});
+		try {
+			cmder.deviceStatusQuery(device, event -> {
+				RequestMessage msg = new RequestMessage();
+				msg.setId(uuid);
+				msg.setKey(key);
+				msg.setData(String.format("获取设备状态失败,错误码: %s, %s", event.statusCode, event.msg));
+				resultHolder.invokeResult(msg);
+			});
+		} catch (InvalidArgumentException | SipException | ParseException e) {
+			logger.error("[命令发送失败] 获取设备状态: {}", e.getMessage());
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
+		}
 		result.onTimeout(()->{
 			logger.warn(String.format("获取设备状态超时"));
 			// 释放rtpserver
@@ -368,14 +376,19 @@ public class DeviceQuery {
 		Device device = storager.queryVideoDevice(deviceId);
 		String key = DeferredResultHolder.CALLBACK_CMD_ALARM + deviceId;
 		String uuid = UUID.randomUUID().toString();
-		cmder.alarmInfoQuery(device, startPriority, endPriority, alarmMethod, alarmType, startTime, endTime, event -> {
-			RequestMessage msg = new RequestMessage();
-			msg.setId(uuid);
-			msg.setKey(key);
-			msg.setData(String.format("设备报警查询失败,错误码: %s, %s",event.statusCode, event.msg));
-			resultHolder.invokeResult(msg);
-		});
-        DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String >> (3 * 1000L);
+		try {
+			cmder.alarmInfoQuery(device, startPriority, endPriority, alarmMethod, alarmType, startTime, endTime, event -> {
+				RequestMessage msg = new RequestMessage();
+				msg.setId(uuid);
+				msg.setKey(key);
+				msg.setData(String.format("设备报警查询失败,错误码: %s, %s",event.statusCode, event.msg));
+				resultHolder.invokeResult(msg);
+			});
+		} catch (InvalidArgumentException | SipException | ParseException e) {
+			logger.error("[命令发送失败] 设备报警查询: {}", e.getMessage());
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
+		}
+		DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String >> (3 * 1000L);
 		result.onTimeout(()->{
 			logger.warn(String.format("设备报警查询超时"));
 			// 释放rtpserver

+ 40 - 16
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/platform/PlatformController.java

@@ -29,6 +29,9 @@ import org.springframework.util.ObjectUtils;
 import org.springframework.web.bind.annotation.*;
 import com.genersoft.iot.vmp.conf.SipConfig;
 
+import javax.sip.InvalidArgumentException;
+import javax.sip.SipException;
+import java.text.ParseException;
 import java.util.List;
 
 /**
@@ -212,20 +215,37 @@ public class PlatformController {
             // 保存时启用就发送注册
             if (parentPlatform.isEnable()) {
                 if (parentPlatformOld != null && parentPlatformOld.isStatus()) {
-                    commanderForPlatform.unregister(parentPlatformOld, null, null);
+                    try {
+                        commanderForPlatform.unregister(parentPlatformOld, null, null);
+                    } catch (InvalidArgumentException | ParseException | SipException e) {
+                        logger.error("[命令发送失败] 国标级联 注销: {}", e.getMessage());
+                    }
                     try {
                         Thread.sleep(500);
                     } catch (InterruptedException e) {
-                        e.printStackTrace();
+                        logger.error("[线程休眠失败] : {}", e.getMessage());
                     }
                     //  只要保存就发送注册
-                    commanderForPlatform.register(parentPlatform, null, null);
+                    try {
+                        commanderForPlatform.register(parentPlatform, null, null);
+                    } catch (InvalidArgumentException | ParseException | SipException e) {
+                        logger.error("[命令发送失败] 国标级联 注册: {}", e.getMessage());
+                    }
+
                 } else {
                     //  只要保存就发送注册
-                    commanderForPlatform.register(parentPlatform, null, null);
+                    try {
+                        commanderForPlatform.register(parentPlatform, null, null);
+                    } catch (InvalidArgumentException | ParseException | SipException e) {
+                        logger.error("[命令发送失败] 国标级联 注册: {}", e.getMessage());
+                    }
                 }
             } else if (parentPlatformOld != null && parentPlatformOld.isEnable() && !parentPlatform.isEnable()) { // 关闭启用时注销
-                commanderForPlatform.unregister(parentPlatformOld, null, null);
+                try {
+                    commanderForPlatform.unregister(parentPlatformOld, null, null);
+                } catch (InvalidArgumentException | ParseException | SipException e) {
+                    logger.error("[命令发送失败] 国标级联 注销: {}", e.getMessage());
+                }
                 // 停止订阅相关的定时任务
                 subscribeHolder.removeAllSubscribe(parentPlatform.getServerGBId());
             }
@@ -258,17 +278,21 @@ public class PlatformController {
             throw new ControllerException(ErrorCode.ERROR100.getCode(), "平台不存在");
         }
         // 发送离线消息,无论是否成功都删除缓存
-        commanderForPlatform.unregister(parentPlatform, (event -> {
-            // 清空redis缓存
-            redisCatchStorage.delPlatformCatchInfo(parentPlatform.getServerGBId());
-            redisCatchStorage.delPlatformKeepalive(parentPlatform.getServerGBId());
-            redisCatchStorage.delPlatformRegister(parentPlatform.getServerGBId());
-        }), (event -> {
-            // 清空redis缓存
-            redisCatchStorage.delPlatformCatchInfo(parentPlatform.getServerGBId());
-            redisCatchStorage.delPlatformKeepalive(parentPlatform.getServerGBId());
-            redisCatchStorage.delPlatformRegister(parentPlatform.getServerGBId());
-        }));
+        try {
+            commanderForPlatform.unregister(parentPlatform, (event -> {
+                // 清空redis缓存
+                redisCatchStorage.delPlatformCatchInfo(parentPlatform.getServerGBId());
+                redisCatchStorage.delPlatformKeepalive(parentPlatform.getServerGBId());
+                redisCatchStorage.delPlatformRegister(parentPlatform.getServerGBId());
+            }), (event -> {
+                // 清空redis缓存
+                redisCatchStorage.delPlatformCatchInfo(parentPlatform.getServerGBId());
+                redisCatchStorage.delPlatformKeepalive(parentPlatform.getServerGBId());
+                redisCatchStorage.delPlatformRegister(parentPlatform.getServerGBId());
+            }));
+        } catch (InvalidArgumentException | ParseException | SipException e) {
+            logger.error("[命令发送失败] 国标级联 注销: {}", e.getMessage());
+        }
 
         boolean deleteResult = storager.deleteParentPlatform(parentPlatform);
         storager.delCatalogByPlatformId(parentPlatform.getServerGBId());

+ 33 - 13
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java

@@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.vmanager.gb28181.play;
 import com.alibaba.fastjson.JSONArray;
 import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.conf.exception.ControllerException;
+import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
 import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
@@ -36,6 +37,9 @@ import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import org.springframework.web.context.request.async.DeferredResult;
 
+import javax.sip.InvalidArgumentException;
+import javax.sip.SipException;
+import java.text.ParseException;
 import java.util.List;
 import java.util.UUID;
 
@@ -102,12 +106,23 @@ public class PlayController {
 			throw new ControllerException(ErrorCode.ERROR400);
 		}
 
+		Device device = storager.queryVideoDevice(deviceId);
+		if (device == null) {
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), "设备[" + deviceId + "]不存在");
+		}
+
 		StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId);
 		if (streamInfo == null) {
 			throw new ControllerException(ErrorCode.ERROR100.getCode(), "点播未找到");
 		}
 
-		cmder.streamByeCmd(deviceId, channelId, streamInfo.getStream(), null, null);
+		try {
+			logger.warn("[停止点播] {}/{}", device.getDeviceId(), channelId);
+			cmder.streamByeCmd(device, channelId, streamInfo.getStream(), null, null);
+		} catch (InvalidArgumentException | SipException | ParseException | SsrcTransactionNotFoundException e) {
+			logger.error("[命令发送失败] 停止点播, 发送BYE: {}", e.getMessage());
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
+		}
 		redisCatchStorage.stopPlay(streamInfo);
 
 		storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
@@ -221,18 +236,23 @@ public class PlayController {
 			resultHolder.invokeResult(msg);
 			return result;
 		}
-		cmder.audioBroadcastCmd(device, (event) -> {
-			RequestMessage msg = new RequestMessage();
-			msg.setKey(key);
-			msg.setId(uuid);
-			JSONObject json = new JSONObject();
-			json.put("DeviceID", deviceId);
-			json.put("CmdType", "Broadcast");
-			json.put("Result", "Failed");
-			json.put("Description", String.format("语音广播操作失败,错误码: %s, %s", event.statusCode, event.msg));
-			msg.setData(json);
-			resultHolder.invokeResult(msg);
-		});
+		try {
+			cmder.audioBroadcastCmd(device, (event) -> {
+				RequestMessage msg = new RequestMessage();
+				msg.setKey(key);
+				msg.setId(uuid);
+				JSONObject json = new JSONObject();
+				json.put("DeviceID", deviceId);
+				json.put("CmdType", "Broadcast");
+				json.put("Result", "Failed");
+				json.put("Description", String.format("语音广播操作失败,错误码: %s, %s", event.statusCode, event.msg));
+				msg.setData(json);
+				resultHolder.invokeResult(msg);
+			});
+		} catch (InvalidArgumentException | SipException | ParseException e) {
+			logger.error("[命令发送失败] 语音广播: {}", e.getMessage());
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
+		}
 
 		result.onTimeout(() -> {
 			logger.warn("语音广播操作超时, 设备未返回应答指令");

+ 34 - 6
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/PlaybackController.java

@@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.vmanager.gb28181.playback;
 
 import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.conf.exception.ControllerException;
+import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
@@ -21,12 +22,15 @@ import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
-import com.alibaba.fastjson.JSONObject;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import org.springframework.web.context.request.async.DeferredResult;
 
+import javax.sip.InvalidArgumentException;
+import javax.sip.SipException;
+import java.text.ParseException;
+
 /**
  * @author lin
  */
@@ -92,7 +96,15 @@ public class PlaybackController {
 		if (ObjectUtils.isEmpty(deviceId) || ObjectUtils.isEmpty(channelId) || ObjectUtils.isEmpty(stream)) {
 			throw new ControllerException(ErrorCode.ERROR400);
 		}
-		cmder.streamByeCmd(deviceId, channelId, stream, null);
+		Device device = storager.queryVideoDevice(deviceId);
+		if (device == null) {
+			throw new ControllerException(ErrorCode.ERROR400.getCode(), "设备:" + deviceId + " 未找到");
+		}
+		try {
+			cmder.streamByeCmd(device, channelId, stream, null);
+		} catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) {
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), "发送bye失败: " + e.getMessage());
+		}
 	}
 
 
@@ -107,7 +119,11 @@ public class PlaybackController {
 			throw new ControllerException(ErrorCode.ERROR400.getCode(), "streamId不存在");
 		}
 		Device device = storager.queryVideoDevice(streamInfo.getDeviceID());
-		cmder.playPauseCmd(device, streamInfo);
+		try {
+			cmder.playPauseCmd(device, streamInfo);
+		} catch (InvalidArgumentException | ParseException | SipException e) {
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage());
+		}
 	}
 
 
@@ -122,7 +138,11 @@ public class PlaybackController {
 			throw new ControllerException(ErrorCode.ERROR400.getCode(), "streamId不存在");
 		}
 		Device device = storager.queryVideoDevice(streamInfo.getDeviceID());
-		cmder.playResumeCmd(device, streamInfo);
+		try {
+			cmder.playResumeCmd(device, streamInfo);
+		} catch (InvalidArgumentException | ParseException | SipException e) {
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage());
+		}
 	}
 
 
@@ -138,7 +158,11 @@ public class PlaybackController {
 			throw new ControllerException(ErrorCode.ERROR400.getCode(), "streamId不存在");
 		}
 		Device device = storager.queryVideoDevice(streamInfo.getDeviceID());
-		cmder.playSeekCmd(device, streamInfo, seekTime);
+		try {
+			cmder.playSeekCmd(device, streamInfo, seekTime);
+		} catch (InvalidArgumentException | ParseException | SipException e) {
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage());
+		}
 	}
 
 	@Operation(summary = "回放倍速播放")
@@ -157,6 +181,10 @@ public class PlaybackController {
 			throw new ControllerException(ErrorCode.ERROR100.getCode(), "不支持的speed(0.25 0.5 1、2、4)");
 		}
 		Device device = storager.queryVideoDevice(streamInfo.getDeviceID());
-		cmder.playSpeedCmd(device, streamInfo, speed);
+		try {
+			cmder.playSpeedCmd(device, streamInfo, speed);
+		} catch (InvalidArgumentException | ParseException | SipException e) {
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage());
+		}
 	}
 }

+ 29 - 9
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/ptz/PtzController.java

@@ -1,6 +1,8 @@
 package com.genersoft.iot.vmp.vmanager.gb28181.ptz;
 
  
+import com.genersoft.iot.vmp.conf.exception.ControllerException;
+import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -18,6 +20,9 @@ import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 
+import javax.sip.InvalidArgumentException;
+import javax.sip.SipException;
+import java.text.ParseException;
 import java.util.UUID;
 
 @Tag(name  = "云台控制")
@@ -98,7 +103,12 @@ public class PtzController {
 			default:
 				break;
 		}
-		cmder.frontEndCmd(device, channelId, cmdCode, horizonSpeed, verticalSpeed, zoomSpeed);
+		try {
+			cmder.frontEndCmd(device, channelId, cmdCode, horizonSpeed, verticalSpeed, zoomSpeed);
+		} catch (SipException | InvalidArgumentException | ParseException e) {
+			logger.error("[命令发送失败] 云台控制: {}", e.getMessage());
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
+		}
 	}
 
 
@@ -117,7 +127,12 @@ public class PtzController {
 		}
 		Device device = storager.queryVideoDevice(deviceId);
 
-		cmder.frontEndCmd(device, channelId, cmdCode, parameter1, parameter2, combindCode2);
+		try {
+			cmder.frontEndCmd(device, channelId, cmdCode, parameter1, parameter2, combindCode2);
+		} catch (SipException | InvalidArgumentException | ParseException e) {
+			logger.error("[命令发送失败] 前端控制: {}", e.getMessage());
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
+		}
 	}
 
 
@@ -146,13 +161,18 @@ public class PtzController {
 			return result;
 		}
 		resultHolder.put(key, uuid, result);
-		cmder.presetQuery(device, channelId, event -> {
-			RequestMessage msg = new RequestMessage();
-			msg.setId(uuid);
-			msg.setKey(key);
-			msg.setData(String.format("获取设备预置位失败,错误码: %s, %s", event.statusCode, event.msg));
-			resultHolder.invokeResult(msg);
-		});
+		try {
+			cmder.presetQuery(device, channelId, event -> {
+				RequestMessage msg = new RequestMessage();
+				msg.setId(uuid);
+				msg.setKey(key);
+				msg.setData(String.format("获取设备预置位失败,错误码: %s, %s", event.statusCode, event.msg));
+				resultHolder.invokeResult(msg);
+			});
+		} catch (InvalidArgumentException | SipException | ParseException e) {
+			logger.error("[命令发送失败] 获取设备预置位: {}", e.getMessage());
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
+		}
 		return result;
 	}
 }

+ 36 - 10
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java

@@ -3,7 +3,9 @@ package com.genersoft.iot.vmp.vmanager.gb28181.record;
 import com.alibaba.fastjson.JSONObject;
 import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.conf.exception.ControllerException;
+import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
+import com.genersoft.iot.vmp.service.IDeviceService;
 import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.service.IPlayService;
 import com.genersoft.iot.vmp.utils.DateUtil;
@@ -31,6 +33,9 @@ import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 
+import javax.sip.InvalidArgumentException;
+import javax.sip.SipException;
+import java.text.ParseException;
 import java.time.LocalDate;
 import java.util.UUID;
 
@@ -54,6 +59,12 @@ public class GBRecordController {
 	@Autowired
 	private IPlayService playService;
 
+	@Autowired
+	private IDeviceService deviceService;
+
+
+
+
 	@Operation(summary = "录像查询")
 	@Parameter(name = "deviceId", description = "设备国标编号", required = true)
 	@Parameter(name = "channelId", description = "通道国标编号", required = true)
@@ -81,13 +92,18 @@ public class GBRecordController {
 		RequestMessage msg = new RequestMessage();
 		msg.setId(uuid);
 		msg.setKey(key);
-		cmder.recordInfoQuery(device, channelId, startTime, endTime, sn, null, null, null, (eventResult -> {
-			WVPResult<RecordInfo> wvpResult = new WVPResult<>();
-			wvpResult.setCode(ErrorCode.ERROR100.getCode());
-			wvpResult.setMsg("查询录像失败, status: " +  eventResult.statusCode + ", message: " + eventResult.msg);
-			msg.setData(wvpResult);
-			resultHolder.invokeResult(msg);
-		}));
+		try {
+			cmder.recordInfoQuery(device, channelId, startTime, endTime, sn, null, null, null, (eventResult -> {
+				WVPResult<RecordInfo> wvpResult = new WVPResult<>();
+				wvpResult.setCode(ErrorCode.ERROR100.getCode());
+				wvpResult.setMsg("查询录像失败, status: " +  eventResult.statusCode + ", message: " + eventResult.msg);
+				msg.setData(wvpResult);
+				resultHolder.invokeResult(msg);
+			}));
+		} catch (InvalidArgumentException | SipException | ParseException e) {
+			logger.error("[命令发送失败] 查询录像: {}", e.getMessage());
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " +  e.getMessage());
+		}
 
 		// 录像查询以channelId作为deviceId查询
 		resultHolder.put(key, uuid, result);
@@ -131,14 +147,24 @@ public class GBRecordController {
 	@GetMapping("/download/stop/{deviceId}/{channelId}/{stream}")
 	public void playStop(@PathVariable String deviceId, @PathVariable String channelId, @PathVariable String stream) {
 
-		cmder.streamByeCmd(deviceId, channelId, stream, null);
-
 		if (logger.isDebugEnabled()) {
 			logger.debug(String.format("设备历史媒体下载停止 API调用,deviceId/channelId:%s_%s", deviceId, channelId));
 		}
 
 		if (deviceId == null || channelId == null) {
-			throw new ControllerException(ErrorCode.ERROR100);
+			throw new ControllerException(ErrorCode.ERROR400);
+		}
+
+		Device device = deviceService.queryDevice(deviceId);
+		if (device == null) {
+			throw new ControllerException(ErrorCode.ERROR400.getCode(), "设备:" + deviceId + "未找到");
+		}
+
+		try {
+			cmder.streamByeCmd(device, channelId, stream, null);
+		} catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) {
+			logger.error("[停止历史媒体下载]停止历史媒体下载,发送BYE失败 {}", e.getMessage());
+			throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage());
 		}
 	}
 

+ 14 - 6
src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiControlController.java

@@ -1,14 +1,20 @@
 package com.genersoft.iot.vmp.web.gb28181;
 
 import com.alibaba.fastjson.JSONObject;
+import com.genersoft.iot.vmp.conf.exception.ControllerException;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
+import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
+import javax.sip.InvalidArgumentException;
+import javax.sip.SipException;
+import java.text.ParseException;
+
 /**
  * API兼容:设备控制
  */
@@ -35,7 +41,7 @@ public class ApiControlController {
      * @return
      */
     @RequestMapping(value = "/ptz")
-    private JSONObject list(String serial,String command,
+    private void list(String serial,String command,
                             @RequestParam(required = false)Integer channel,
                             @RequestParam(required = false)String code,
                             @RequestParam(required = false)Integer speed){
@@ -48,9 +54,7 @@ public class ApiControlController {
         if (speed == null) {speed = 0;}
         Device device = storager.queryVideoDevice(serial);
         if (device == null) {
-            JSONObject result = new JSONObject();
-            result.put("error","device[ " + serial + " ]未找到");
-            return result;
+            throw new ControllerException(ErrorCode.ERROR100.getCode(), "device[ " + serial + " ]未找到");
         }
         int cmdCode = 0;
         switch (command){
@@ -91,7 +95,11 @@ public class ApiControlController {
                 break;
         }
         // 默认值 50
-        cmder.frontEndCmd(device, code, cmdCode, speed, speed, speed);
-        return null;
+        try {
+            cmder.frontEndCmd(device, code, cmdCode, speed, speed, speed);
+        } catch (SipException | InvalidArgumentException | ParseException e) {
+            logger.error("[命令发送失败] 云台控制: {}", e.getMessage());
+            throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
+        }
     }
 }

+ 22 - 1
src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiStreamController.java

@@ -3,10 +3,12 @@ package com.genersoft.iot.vmp.web.gb28181;
 import com.alibaba.fastjson.JSONObject;
 import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.conf.UserSetting;
+import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import com.genersoft.iot.vmp.service.IDeviceService;
 import com.genersoft.iot.vmp.service.IPlayService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
@@ -17,6 +19,10 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.context.request.async.DeferredResult;
 
+import javax.sip.InvalidArgumentException;
+import javax.sip.SipException;
+import java.text.ParseException;
+
 /**
  * API兼容:实时直播
  */
@@ -40,6 +46,9 @@ public class ApiStreamController {
     @Autowired
     private IRedisCatchStorage redisCatchStorage;
 
+    @Autowired
+    private IDeviceService deviceService;
+
     @Autowired
     private IPlayService playService;
 
@@ -177,7 +186,19 @@ public class ApiStreamController {
             result.put("error","未找到流信息");
             return result;
         }
-        cmder.streamByeCmd(serial, code, streamInfo.getStream(), null);
+        Device device = deviceService.queryDevice(serial);
+        if (device == null) {
+            JSONObject result = new JSONObject();
+            result.put("error","未找到设备");
+            return result;
+        }
+        try {
+            cmder.streamByeCmd(device, code, streamInfo.getStream(), null);
+        } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) {
+            JSONObject result = new JSONObject();
+            result.put("error","发送BYE失败:" + e.getMessage());
+            return result;
+        }
         redisCatchStorage.stopPlay(streamInfo);
         storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
         return null;