Browse Source

招商视频墙NVR9对接初步开发

zhangqiankun 3 năm trước cách đây
mục cha
commit
99f5dffafa
24 tập tin đã thay đổi với 1136 bổ sung104 xóa
  1. 149 0
      config/scene/nvr_scene_1_1.json
  2. 5 0
      pom.xml
  3. 38 0
      src/main/java/com/persagy/cameractl/cache/SceneConfigCache.java
  4. 5 11
      src/main/java/com/persagy/cameractl/conf/CameraConfig.java
  5. 5 10
      src/main/java/com/persagy/cameractl/conf/NVR9Config.java
  6. 55 14
      src/main/java/com/persagy/cameractl/controller/HelloController.java
  7. 123 0
      src/main/java/com/persagy/cameractl/init/SceneConfigInit.java
  8. 8 10
      src/main/java/com/persagy/cameractl/init/SystemInit.java
  9. 61 0
      src/main/java/com/persagy/cameractl/model/Camera.java
  10. 12 16
      src/main/java/com/persagy/cameractl/model/Channel.java
  11. 39 0
      src/main/java/com/persagy/cameractl/model/NvrSceneRelation.java
  12. 54 0
      src/main/java/com/persagy/cameractl/model/SceneConfig.java
  13. 4 4
      src/main/java/com/persagy/cameractl/service/PtzMain.java
  14. 56 0
      src/main/java/com/persagy/cameractl/service/socket/TclClientHandler.java
  15. 67 0
      src/main/java/com/persagy/cameractl/service/socket/TclSocketClient.java
  16. 22 0
      src/main/java/com/persagy/cameractl/service/socket/TestHandler.java
  17. 56 0
      src/main/java/com/persagy/cameractl/service/socket/TestServer.java
  18. 30 38
      src/main/java/com/persagy/cameractl/service/windows/NVR9MainWindows.java
  19. 264 0
      src/main/java/com/persagy/cameractl/service/windows/Nvr9CameraExecuteApi.java
  20. 20 0
      src/main/java/com/persagy/vsknet/structure/CurShowDevEx.java
  21. 18 0
      src/main/java/com/persagy/vsknet/structure/DECV2_PROTO_IPC.java
  22. 19 0
      src/main/java/com/persagy/vsknet/structure/DECV2_PROTO_SPLIT_PREVIEW.java
  23. 25 0
      src/main/java/com/persagy/vsknet/structure/TagChangeWndStyle.java
  24. 1 1
      src/main/resources/application.yml

+ 149 - 0
config/scene/nvr_scene_1_1.json

@@ -0,0 +1,149 @@
+{
+	"0": {
+		"channelId": 0,
+		"ip": "10.0.254.12",
+		"port": 7000,
+		"username": "admin",
+		"password": "12345"
+	},
+	"1": {
+		"channelId": 1,
+		"ip": "10.0.254.12",
+		"port": 7000,
+		"username": "admin",
+		"password": "12345"
+	},
+	"2": {
+		"channelId": 2,
+		"ip": "10.0.254.12",
+		"port": 7000,
+		"username": "admin",
+		"password": "12345"
+	},
+	"3": {
+		"channelId": 3,
+		"ip": "10.0.254.12",
+		"port": 7000,
+		"username": "admin",
+		"password": "12345"
+	},
+	"4": {
+		"channelId": 4,
+		"ip": "10.0.254.12",
+		"port": 7000,
+		"username": "admin",
+		"password": "12345"
+	},
+	"5": {
+		"channelId": 5,
+		"ip": "10.0.254.12",
+		"port": 7000,
+		"username": "admin",
+		"password": "12345"
+	},
+	"6": {
+		"channelId": 6,
+		"ip": "10.0.254.12",
+		"port": 7000,
+		"username": "admin",
+		"password": "12345"
+	},
+	"7": {
+		"channelId": 7,
+		"ip": "10.0.254.12",
+		"port": 7000,
+		"username": "admin",
+		"password": "12345"
+	},
+	"8": {
+		"channelId": 8,
+		"ip": "10.0.254.12",
+		"port": 7000,
+		"username": "admin",
+		"password": "12345"
+	},
+	"9": {
+		"channelId": 9,
+		"ip": "10.0.254.12",
+		"port": 7000,
+		"username": "admin",
+		"password": "12345"
+	},
+	"10": {
+		"channelId": 10,
+		"ip": "10.0.254.12",
+		"port": 7000,
+		"username": "admin",
+		"password": "12345"
+	},
+	"11": {
+		"channelId": 11,
+		"ip": "10.0.254.12",
+		"port": 7000,
+		"username": "admin",
+		"password": "12345"
+	},
+	"12": {
+		"channelId": 12,
+		"ip": "10.0.254.12",
+		"port": 7000,
+		"username": "admin",
+		"password": "12345"
+	},
+	"13": {
+		"channelId": 13,
+		"ip": "10.0.254.12",
+		"port": 7000,
+		"username": "admin",
+		"password": "12345"
+	},
+	"14": {
+		"channelId": 14,
+		"ip": "10.0.254.12",
+		"port": 7000,
+		"username": "admin",
+		"password": "12345"
+	},
+	"15": {
+		"channelId": 15,
+		"ip": "10.0.254.12",
+		"port": 7000,
+		"username": "admin",
+		"password": "12345"
+	},
+	"16": {
+		"channelId": 16,
+		"ip": "10.0.254.12",
+		"port": 7000,
+		"username": "admin",
+		"password": "12345"
+	},
+	"17": {
+		"channelId": 17,
+		"ip": "10.0.254.12",
+		"port": 7000,
+		"username": "admin",
+		"password": "12345"
+	},
+	"18": {
+		"channelId": 18,
+		"ip": "10.0.254.12",
+		"port": 7000,
+		"username": "admin",
+		"password": "12345"
+	},
+	"19": {
+		"channelId": 19,
+		"ip": "10.0.254.12",
+		"port": 7000,
+		"username": "admin",
+		"password": "12345"
+	},
+	"20": {
+		"channelId": 20,
+		"ip": "10.0.254.12",
+		"port": 7000,
+		"username": "admin",
+		"password": "12345"
+	}
+}

+ 5 - 0
pom.xml

@@ -90,6 +90,11 @@
 		</dependency>
 
 		<dependency>
+			<groupId>io.netty</groupId>
+			<artifactId>netty-all</artifactId>
+		</dependency>
+		
+		<dependency>
 			<groupId>org.projectlombok</groupId>
 			<artifactId>lombok</artifactId>
 		</dependency>

+ 38 - 0
src/main/java/com/persagy/cameractl/cache/SceneConfigCache.java

@@ -0,0 +1,38 @@
+package com.persagy.cameractl.cache;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.persagy.cameractl.model.NvrSceneRelation;
+
+/**
+ * 场景配置缓存
+ * 
+ * @version 1.0.0
+ * @company persagy 
+ * @author zhangqiankun
+ * @date 2021年10月14日 下午5:36:25
+ */
+public class SceneConfigCache {
+	public static final String UNDERLINE = "_";
+	
+	/**
+	 * key: 精确到场景配置中的每个场景窗口的ID: group_scene_windowId
+	 * value: 解码器相关参数
+	 */
+	public static final Map<String, NvrSceneRelation> SCENE_CONFIG = new HashMap<String, NvrSceneRelation>();
+	
+	/**
+	 * 获取缓存key
+	 * 
+	 * @param group 组ID
+	 * @param scene 场景ID
+	 * @param windowId 场景配置中每一个场景窗口的ID
+	 * @return
+	 * @date 2021年10月14日 下午7:57:19
+	 */
+	public static String getCacheKey(int group, int scene, int windowId) {
+		return String.valueOf(group) + UNDERLINE + String.valueOf(scene) + UNDERLINE + String.valueOf(windowId);
+	}
+	
+}

+ 5 - 11
src/main/java/com/persagy/cameractl/conf/CameraConfig.java

@@ -36,16 +36,10 @@ public class CameraConfig {
 	private String zhaoshangTvScreenId;
 	
 	
-	@Value("${nvr9-config.ip}")
-	private String nvr9Ip;
-	
-	@Value("${nvr9-config.port}")
-	private long nvr9Port;
-	
-	@Value("${nvr9-config.user-name}")
-	private String nvr9UserName;
-	
-	@Value("${nvr9-config.bajiuSdkPort}")
-	private String nvr9Password;
+	@Value("${tcl.ip}")
+	private String tclIp;
 
+	@Value("${tcl.port}")
+	private int tclPort;
+	
 }

+ 5 - 10
src/main/java/com/persagy/cameractl/conf/NVR9Config.java

@@ -18,7 +18,7 @@ import com.sun.jna.platform.win32.WinDef.UINT;
  */
 public class NVR9Config {
 	/** 项目根路径 */
-	public static final String SERVER_ROOT_PATH = System.getProperty("user.dir");
+	public static final String CONFIG_ROOT_PATH = System.getProperty("user.dir") + "/config/";
 
 	/** NVR 9000 Client */
 	public static VskDevNet vskDevNet = null;
@@ -67,13 +67,8 @@ public class NVR9Config {
 	// 回放时生成的回放文件路径,包含文件名称
 	public static String playBackFilePath;
 	
-	public static String nvr9Ip;
-	
-	public static long nvr9Port;
-	
-	public static String nvr9UserName;
-	
-	public static String nvr9Password;
+	public static String tclIp;
+	public static int tclPort;
 
 	/**
 	 * 获取指定的 dll 文件路径
@@ -83,7 +78,7 @@ public class NVR9Config {
 	 * @date 2021年9月29日 下午3:23:37
 	 */
 	public static String getWindowsDllPath(String dllName) {
-		File directory = new File(SERVER_ROOT_PATH + "/config/nvr9000/windows/" + dllName);
+		File directory = new File(CONFIG_ROOT_PATH + "nvr9000/windows/" + dllName);
 		String filePath = "";
 		try {
 			filePath = directory.getCanonicalPath();
@@ -101,7 +96,7 @@ public class NVR9Config {
 	 * @date 2021年9月29日 下午3:23:37
 	 */
 	public static String getLinuxDllPath(String dllName) {
-		File directory = new File(SERVER_ROOT_PATH + "/config/nvr9000/linux/" + dllName);
+		File directory = new File(CONFIG_ROOT_PATH + "nvr9000/linux/" + dllName);
 		String filePath = "";
 		try {
 			filePath = directory.getCanonicalPath();

+ 55 - 14
src/main/java/com/persagy/cameractl/controller/HelloController.java

@@ -7,15 +7,17 @@ import java.io.OutputStream;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.CrossOrigin;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
 
+import com.persagy.cameractl.model.SceneConfig;
 import com.persagy.cameractl.service.PtzMain;
+import com.persagy.cameractl.service.windows.Nvr9CameraExecuteApi;
 import com.persagy.cameractl.utils.Camera;
 import com.persagy.cameractl.utils.OtherTools;
 import com.persagy.cameractl.utils.ResultClass;
@@ -25,8 +27,8 @@ import com.persagy.cameractl.utils.StringTools;
 import lombok.extern.slf4j.Slf4j;
 
 @Slf4j
-@Controller
 @CrossOrigin
+@RestController
 public class HelloController {
 
 	@RequestMapping(value = "/hello/{name}", method = RequestMethod.GET, produces = "application/json;charset=UTF-8")
@@ -47,7 +49,7 @@ public class HelloController {
 	 * startDateStr、endDateStr
 	 */
 	@RequestMapping(value = "/sdk/{opertype}", method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
-	public @ResponseBody String ptzOperation(@PathVariable("opertype") String operType, @RequestBody Camera _camera) {
+	public String ptzOperation(@PathVariable("opertype") String operType, @RequestBody Camera _camera) {
 		PtzMain pcControlMain = new PtzMain();
 		ResultClass result = pcControlMain.ptzOper(_camera, operType);
 		switch (String.valueOf(result.name)) {
@@ -64,11 +66,8 @@ public class HelloController {
 	 * 请求视频文件接口
 	 */
 	@RequestMapping(value = "/vplayf/{token}", method = RequestMethod.GET, produces = "application/json;charset=UTF-8")
-	public @ResponseBody void vplay(@PathVariable("token") String token, HttpServletResponse response,
+	public void vplay(@PathVariable("token") String token, HttpServletResponse response,
 			HttpServletRequest request) {
-//		String rangeStr=request.getHeader("Range");
-//		if(rangeStr.lastIndexOf("-")!=rangeStr.length()-1)
-//			return;
 		String filePath = OtherTools.getVideoFilePathByT(token);
 		if (filePath.equals(""))
 			return;
@@ -83,14 +82,8 @@ public class HelloController {
 			response.setContentType("video/mp4");
 			response.setHeader("Content-Disposition", "attachment; filename=\"" + diskfilename + "\"");
 			response.setContentLength(data.length);
-			// 支持分段请求
-//			response.setHeader("Accept-Ranges", "bytes");
 			// 不支持分段请求
 			response.setHeader("Accept-Ranges", "none");
-			// response.setHeader("Content-Range", "" + Integer.valueOf(data.length - 1));
-//			response.setHeader("Content-Range", "" + data.length);
-//			response.setHeader("Range", "bytes=0-" + data.length);
-//			response.setHeader("Content-Range", "bytes 0-" + data.length+"/"+data.length);
 			response.setHeader("Etag", "W/\"" + StringTools.getUUID() + "\"");
 			OutputStream os = response.getOutputStream();
 
@@ -103,6 +96,54 @@ public class HelloController {
 		} catch (Exception e) {
 			log.error("播放" + token + "异常:", e);
 		}
-
 	}
+	
+    /*
+     * 调用软件平台接口,根据摄像头监控点位获取预览流url、回放流url、云台控制
+     * operType: preview 预览	playback 回放	control 云台控制	other 支持传入接口名称和参数,方便调试	tvwall 获取视频墙列表
+     * 		tvwalllayout 获取某个视频墙的布局	toup 投屏,即上墙操作	exittoup 退出投屏
+     * 		kaichuang 在电视墙屏幕上进行开窗  setwinsource 设置窗口视频源
+     * 	预览参数:
+     * 		cameraIndexCode(视频通道编码,例1000000$1$0$0)、streamType(码流类型,可不传,不传时默认1; 0:主码流 1:子码流 2:第三码流)、levelNo(级联号,没有时不传)
+     *	回放参数:
+     *		cameraIndexCode(视频通道编码,例1000000$1$0$0)、streamType(码流类型,可不传,不传时默认1; 0:主码流 1:子码流 2:第三码流)、levelNo(级联号,没有时不传)
+     *		startDateStr(开始时间,2021-04-01 11:12:13)、endDateStr(结束时间,2021-04-01 11:12:13)
+     *	云台控制参数:
+     *		cameraIndexCode(视频通道编码,例1000000$1$0$0)、command(控制命令)、speed(速度,值范围1~8,默认8)
+     *
+     *	other 根据传入接口名称来调用对应的大华软件平台接口,参数:
+     *		url 调用的大华软件平台接口名称,例:admin/rest/api
+     *		paramStr 自定义参数,格式:{_type:"get | post",interfaceId:"可为空",isToken:"1 url上需要拼接token | 0 url上不需要拼接token"}
+     *		jsonParam 传给大华软件平台的接口参数;例:"{\"param\":{\"category\":1}}"
+     *
+     *	获取视频墙列表参数
+     *		无
+     *	获取某个视频墙的布局参数
+     *		tvWallId 视频墙ID
+     *	投屏参数
+     *		layoutCode	one(只有一个窗口)、left-up-one(六个窗口,左上角是大窗口)、right-down-one(六个窗口,右下角是大窗口)、right-up-one(六个窗口,右上角是大窗口)、left-down-one(六个窗口,左下角是大窗口)、multi-one(九宫格窗口)
+     *		cameraIndexArr 视频墙数据源数组,每一项均为cameraIndexCode,数组的顺序和窗口顺序保持一致
+     */
+    @RequestMapping(value = "/vision/{opertype}", method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
+    public String hikvisionOper(@PathVariable("opertype") String operType, @RequestBody SceneConfig sceneConfig) {
+
+        log.info("调用NVR9平台接口开始,操作类型:{},场景配置:{}", operType, sceneConfig);
+        long start = System.currentTimeMillis();
+
+        Nvr9CameraExecuteApi dahCameraExecuteApi = new Nvr9CameraExecuteApi();
+        
+        ResultClass executeResult = operType.equals("currentVideo") ? dahCameraExecuteApi.currentVideo(sceneConfig.getNvrSceneRelation())
+                : operType.equals("toup") ? dahCameraExecuteApi.toup(sceneConfig) 
+                	: new ResultClass(false, "非法请求");
+
+        switch (String.valueOf(executeResult.name)) {
+            case "true":
+            	log.info("调用NVR9平台接口success,返回值:{},耗时:{} ms", ResultTools.dataResult(executeResult.resultData), (System.currentTimeMillis() - start));
+                return ResultTools.dataResult(executeResult.resultData);
+            default:
+            	log.warn("调用NVR9平台接口success,返回值:{},耗时:{} ms", ResultTools.dataResult(executeResult.reason), (System.currentTimeMillis() - start));
+                return ResultTools.errorResult(executeResult.reason);
+        }
+    }
+
 }

+ 123 - 0
src/main/java/com/persagy/cameractl/init/SceneConfigInit.java

@@ -0,0 +1,123 @@
+package com.persagy.cameractl.init;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.rmi.ServerException;
+import java.util.Set;
+
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.stereotype.Component;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.parser.Feature;
+import com.persagy.cameractl.cache.SceneConfigCache;
+import com.persagy.cameractl.conf.NVR9Config;
+import com.persagy.cameractl.model.NvrSceneRelation;
+
+/**
+ * 项目启动后,加载场景配置
+ * 
+ * @version 1.0.0
+ * @company persagy 
+ * @author zhangqiankun
+ * @date 2021年10月14日 下午5:09:18
+ */
+@Component
+public class SceneConfigInit implements CommandLineRunner {
+	
+	@Override
+	public void run(String... args) throws Exception {
+		// 获取所有的场景配置文件
+		File configPath = new File(NVR9Config.CONFIG_ROOT_PATH + "scene");
+		if (!configPath.exists()) {
+			configPath.mkdirs();
+		}
+		
+		File[] listFiles = configPath.listFiles();
+		this.loadSceneConfig(listFiles);
+	}
+	
+	/**
+	 * 文件加载,并放入本地缓存
+	 * 
+	 * @param listFiles
+	 * @throws IOException 
+	 * @date 2021年10月14日 下午5:35:03
+	 */
+	private void loadSceneConfig(File[] listFiles) throws IOException {
+		if (listFiles == null || listFiles.length == 0) {
+			throw new ServerException("不存在,场景配置文件,请预先配置");
+		}
+		
+		for (File file : listFiles) {
+			String name = file.getName();
+			if (name != null && name.startsWith("nvr_scene_") && name.endsWith(".json")) {
+				int indexOf = name.indexOf(".");
+				String groupStr = name.substring(indexOf - 3, indexOf - 2);
+				String sceneStr = name.substring(indexOf - 1, indexOf);
+				int group = Integer.valueOf(groupStr);
+				int scene = Integer.valueOf(sceneStr);
+				
+				// 获取对应的解码器配置信息,放入本地缓存
+				JSONObject fileJSON = this.readFileInfo(file);
+				Set<String> windowIds = fileJSON.keySet();
+				for (String windowIdStr : windowIds) {
+					int windowId = Integer.valueOf(windowIdStr);
+					JSONObject sdkInfo = fileJSON.getJSONObject(windowIdStr);
+					
+					NvrSceneRelation relation = new NvrSceneRelation();
+					relation.setGroup(group);
+					relation.setScene(scene);
+					relation.setSdkChannelId(sdkInfo.getByteValue("channelId"));
+					relation.setIp(sdkInfo.getString("ip"));
+					relation.setPort(sdkInfo.getIntValue("port"));
+					relation.setUsername(sdkInfo.getString("username"));
+					relation.setPassword(sdkInfo.getString("password"));
+					
+					SceneConfigCache.SCENE_CONFIG.put(SceneConfigCache.getCacheKey(group, scene, windowId), relation);
+				}
+			}
+		}
+	}
+	
+	/**
+	 * 读取文件内容,返回文件信息
+	 * 
+	 * @return
+	 * @throws IOException 
+	 * @date 2021年10月14日 下午5:37:29
+	 */
+	private JSONObject readFileInfo(File file) throws IOException {
+		return JSON.parseObject(new FileInputStream(file), StandardCharsets.UTF_8, JSONObject.class,
+                // 自动关闭流
+                Feature.AutoCloseSource,
+                // 允许注释
+                Feature.AllowComment,
+                // 允许单引号
+                Feature.AllowSingleQuotes);
+		
+	}
+	
+	/**
+	 * 测试
+	 * 
+	 * @param args
+	 * @throws IOException
+	 * @date 2021年10月14日 下午6:32:12
+	 */
+	public static void main(String[] args) throws IOException {
+		SceneConfigInit sceneConfigInit = new SceneConfigInit();
+		// 获取所有的场景配置文件
+		File configPath = new File(System.getProperty("user.dir") + "/config/scene");
+		if (!configPath.exists()) {
+			configPath.mkdirs();
+		}
+		
+		File[] listFiles = configPath.listFiles();
+		sceneConfigInit.loadSceneConfig(listFiles);
+	}
+	
+}

+ 8 - 10
src/main/java/com/persagy/cameractl/init/SystemInit.java

@@ -42,11 +42,9 @@ public class SystemInit implements ApplicationRunner {
 		AllStaticConfig.zhaoshangTvScreenId = cameraConfig.getZhaoshangTvScreenId();
 
 		// NVR9000
-		NVR9Config.nvr9Ip= cameraConfig.getNvr9Ip();
-		NVR9Config.nvr9Port= cameraConfig.getNvr9Port();
-		NVR9Config.nvr9UserName= cameraConfig.getNvr9UserName();
-		NVR9Config.nvr9Password= cameraConfig.getNvr9Password();
-		
+		NVR9Config.tclIp = cameraConfig.getTclIp();
+		NVR9Config.tclPort = cameraConfig.getTclPort();
+
 		this.initSdk();
 		this.setCustomCallBak();
 
@@ -59,7 +57,7 @@ public class SystemInit implements ApplicationRunner {
 	/**
 	 * 初始化SDK
 	 * 
-	 * @throws InstantiationException 
+	 * @throws InstantiationException
 	 * @date 2021年9月29日 下午8:38:35
 	 */
 	private void initSdk() throws InstantiationException {
@@ -68,7 +66,7 @@ public class SystemInit implements ApplicationRunner {
 		System.load(AllStaticConfig.getLibfaacPath());
 		System.load(AllStaticConfig.getG711SoundPlayPath());
 		System.load(AllStaticConfig.getDecoderLibPath());
-		
+
 		// 初始化客户端
 		Map<String, Object> options = new HashMap<>();
 		options.put(Library.OPTION_FUNCTION_MAPPER, new SdkMapper());
@@ -77,14 +75,14 @@ public class SystemInit implements ApplicationRunner {
 		if (initResult != 0) {
 			throw new InstantiationException("init SDKJsonDll failure:" + initResult);
 		}
-		
+
 		// 加载NVR9000 音频解码库
 		System.load(NVR9Config.getWindowsDllPath(NVR9Config.G711SOUNDPLAY_DLL));
-		
+
 		// 初始化客户端
 		Map<String, Object> nvr9Options = new HashMap<>();
 		nvr9Options.put(Library.OPTION_FUNCTION_MAPPER, new com.persagy.vsknet.SdkMapper());
-		
+
 		String windowsDllPath = NVR9Config.getWindowsDllPath(NVR9Config.NSDEVNET_DLL);
 		NVR9Config.vskDevNet = (VskDevNet) Native.load(windowsDllPath, VskDevNet.class);
 		Boolean initate = NVR9Config.vskDevNet.NS_NET_Initate();

+ 61 - 0
src/main/java/com/persagy/cameractl/model/Camera.java

@@ -0,0 +1,61 @@
+package com.persagy.cameractl.model;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+/**
+ * 
+ * @version 1.0.0
+ * @company persagy 
+ * @author zhangqiankun
+ * @date 2021年10月8日 下午6:10:56
+ */
+@Getter
+@Setter
+@ToString
+public class Camera {
+	
+	/** ip */
+	private String ip;
+	
+	/** port */
+	private int port;
+	
+	/** 用户名 */
+	private String username;
+	
+	/** 密码 */
+	private String password;
+	
+	/** 通道号1-64 */
+	private int wChanUsed;
+	
+	/** 通道ID */
+	private byte channelID;
+	
+	/** 0-主码流,1-子码流 */
+	private byte streamType;
+	
+	/** 子码流的地址 */
+	private String deviceAddrs;
+	
+	/** 标示device_addrs的实际大小 */
+	private int addrsSize;
+	
+	/** 主码流的地址 */
+	private String streamUri;
+	
+	/** 标示streamUri的实际大小 */
+	private int uriSize;
+	
+	/** 默认填写为25。 */
+	private byte frameRate = 25;
+	
+	/** 厂商名称 */
+	private String manufactername;
+	
+	/** 设备型号 */
+	private String model;
+	
+}

+ 12 - 16
src/main/java/com/persagy/cameractl/model/Channel.java

@@ -1,33 +1,29 @@
 package com.persagy.cameractl.model;
 
+import java.util.List;
+
 import lombok.Getter;
 import lombok.Setter;
+import lombok.ToString;
 
 /**
  * 
  * @version 1.0.0
  * @company persagy 
  * @author zhangqiankun
- * @date 2021年10月8日 下午6:10:56
+ * @date 2021年10月14日 上午10:33:23
  */
 @Getter
 @Setter
+@ToString
 public class Channel {
 
-	private String username;
-	private String password;
-	private int wChanUsed;
-	private byte channelID;
-	private String deviceAddrs;
-	private byte streamType;		// 0-主,1-子
-	/** 标示device_addrs数组的实际有效值长度。 */
-	private int addrsSize;
-	private String streamUri;
-	private int uriSize;
-	private String ip;
-	private int port;
-	private byte frameRate = 25;
-	private String qstrmanufactername;
-	private String qstrmodel;
+	/** 每个场景中的窗口ID,从左到右,从上到下,从0开始 */
+	private int windowId;
+	
+	/** 摄像头信息集合 */
+	private List<Camera> cameras;
+	
+	
 	
 }

+ 39 - 0
src/main/java/com/persagy/cameractl/model/NvrSceneRelation.java

@@ -0,0 +1,39 @@
+package com.persagy.cameractl.model;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 解码器输出口与TCL入口号之间的关系
+ * 
+ * @version 1.0.0
+ * @company persagy 
+ * @author zhangqiankun
+ * @date 2021年10月14日 下午6:40:01
+ */
+@Getter
+@Setter
+public class NvrSceneRelation {
+
+	/** 组ID */
+	private int group;
+	
+	/** 场景ID */
+	private int scene;
+	
+	/** 解码器输出口好,从0开始 */
+	private byte sdkChannelId;
+	
+	/** 解码器的登录IP */
+	private String ip;
+	
+	/** 解码器的登录PORT */
+	private int port;
+	
+	/** 解码器的登录用户名 */
+	private String username;
+	
+	/** 解码器的登录密码 */
+	private String password;
+	
+}

+ 54 - 0
src/main/java/com/persagy/cameractl/model/SceneConfig.java

@@ -0,0 +1,54 @@
+package com.persagy.cameractl.model;
+
+import java.util.List;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+/**
+ * 根据预设置的场景,将摄像头绑定至对应的场景窗口上
+ * 
+ * @version 1.0.0
+ * @company persagy 
+ * @author zhangqiankun
+ * @date 2021年10月14日 上午10:27:42
+ */
+@Getter
+@Setter
+@ToString
+public class SceneConfig {
+
+	/** 组ID */
+	private int group;
+	
+	/** 场景ID */
+	private int scene;
+	
+	/** 轮巡时间,单位:秒 */
+	private String nchanggeTime;
+	
+	/** 时间段ID,取值 1,2,3 */
+	private String ntimeindex;
+	
+	/** 星期,从星期日开始0-6,全部为7 */
+	private String nweek;
+	
+	/** 窗口分割数量 */
+	private String nwndnumber;
+	
+	/** 开始时间,距离:00:00 的秒数 */
+	private String ulstarttime;
+	
+	/** 结束时间,距离:00:00 的秒数 */
+	private String ulendtime;
+	
+	/** 输出屏号 */
+	private String noutputscreenno;
+	
+	/** 通道集合 */
+	private List<Channel> channels;
+	
+	private NvrSceneRelation nvrSceneRelation;
+	
+}

+ 4 - 4
src/main/java/com/persagy/cameractl/service/PtzMain.java

@@ -1,9 +1,7 @@
 package com.persagy.cameractl.service;
 
-import com.persagy.cameractl.service.windows.NVR9MainWindows;
 import com.persagy.cameractl.service.windows.ZhaosMainWindows;
 import com.persagy.cameractl.utils.Camera;
-import com.persagy.cameractl.utils.EnumTools.OperatingSystem;
 import com.persagy.cameractl.utils.OtherTools;
 import com.persagy.cameractl.utils.ResultClass;
 
@@ -24,9 +22,11 @@ public class PtzMain {
 			return zhaosMainWindows.playBackMain();
 		case "searchlog":
 			return zhaosMainWindows.searchLogMain();
+		default:
+			return OtherTools.executeErr("暂不支持");
 		}
 		
-		OperatingSystem systemName = OtherTools.getSystemName();
+		/*OperatingSystem systemName = OtherTools.getSystemName();
 		switch (systemName) {
 		case windows:
 			NVR9MainWindows nvr9MainWindows = new NVR9MainWindows(_camera);
@@ -34,7 +34,7 @@ public class PtzMain {
 					: OtherTools.executeErr("无效的操作类型");
 		default:
 			return OtherTools.executeErr("暂不支持");
-		}
+		}*/
 	};
 
 	/**

+ 56 - 0
src/main/java/com/persagy/cameractl/service/socket/TclClientHandler.java

@@ -0,0 +1,56 @@
+package com.persagy.cameractl.service.socket;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.SimpleChannelInboundHandler;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 
+ * @version 1.0.0
+ * @company persagy 
+ * @author zhangqiankun
+ * @date 2021年10月12日 上午10:20:11
+ */
+@Slf4j
+public class TclClientHandler extends SimpleChannelInboundHandler<String> {
+
+	/** 需要调用的指令信息,后缀需携带 结束符或回车 \r */
+	private final String command;
+	
+    public TclClientHandler(String command) {
+    	this.command = command;
+	}
+
+	/**
+     * 向服务端发送数据
+     */
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) throws Exception {
+        ctx.writeAndFlush(command);
+    }
+
+    /**
+     * 接收数据
+     */
+    @Override
+    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
+        log.info("客户端接收到的服务端信息:" + msg);
+        ctx.close();
+    }
+	
+    /**
+     * channel 通道 Inactive 不活跃的
+     *
+     * 当客户端主动断开服务端的链接后,这个通道就是不活跃的。也就是说客户端与服务端的关闭了通信通道并且不可以传输数据
+     */
+    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+        log.info("客户端与服务端通道关闭:" + ctx.channel().remoteAddress());
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+    	log.warn("通道异常退出", cause);
+        ctx.close();
+    }
+    
+}

+ 67 - 0
src/main/java/com/persagy/cameractl/service/socket/TclSocketClient.java

@@ -0,0 +1,67 @@
+package com.persagy.cameractl.service.socket;
+
+import java.net.InetSocketAddress;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.codec.string.StringDecoder;
+import io.netty.handler.codec.string.StringEncoder;
+import io.netty.util.CharsetUtil;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 
+ * @version 1.0.0
+ * @company persagy
+ * @author zhangqiankun
+ * @date 2021年10月12日 上午10:06:34
+ */
+@Slf4j
+public class TclSocketClient {
+
+	public static final EventLoopGroup GROUP = new NioEventLoopGroup();
+
+	public static final Bootstrap BS = new Bootstrap();
+
+	public static void connectAndSend(String host, int port, String command) {
+		ChannelFuture cf = null;
+		try {
+			BS.group(GROUP) // 注册线程池
+					.channel(NioSocketChannel.class) // 使用NioSocketChannel来作为连接用的channel类
+					.remoteAddress(new InetSocketAddress(host, port)) // 绑定连接端口和host信息
+					.handler(new ChannelInitializer<SocketChannel>() { // 绑定连接初始化器
+						@Override
+						protected void initChannel(SocketChannel ch) throws Exception {
+							log.info("正在连接中...");
+							ch.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8));
+							ch.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8));
+							ch.pipeline().addLast(new TclClientHandler(command));
+						}
+					});
+
+			cf = BS.connect().sync(); // 异步连接服务器
+			log.info("服务端连接成功...");
+		} catch (Exception e) {
+			log.error("与服务端建立连接失败", e);
+		} finally {
+			if (cf != null) {
+				try {
+					cf.channel().closeFuture().sync();
+					log.info("连接已关闭..");
+				} catch (Exception e2) {
+					log.error("未知异常", e2);
+				}
+			}
+		}
+	}
+
+	public static void main(String[] args) throws Exception {
+		TclSocketClient.connectAndSend("192.168.0.27", 8888, "gload 1 2\r");
+	}
+	
+}

+ 22 - 0
src/main/java/com/persagy/cameractl/service/socket/TestHandler.java

@@ -0,0 +1,22 @@
+package com.persagy.cameractl.service.socket;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.SimpleChannelInboundHandler;
+
+/**
+ * 
+ * @version 1.0.0
+ * @company persagy 
+ * @author zhangqiankun
+ * @date 2021年10月12日 上午10:54:49
+ */
+
+public class TestHandler extends SimpleChannelInboundHandler<String> {
+
+	@Override
+	protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
+		System.out.println("服务端接收到的消息: " + msg);
+		ctx.writeAndFlush("这是服务端发送的");
+	}
+	
+}

+ 56 - 0
src/main/java/com/persagy/cameractl/service/socket/TestServer.java

@@ -0,0 +1,56 @@
+package com.persagy.cameractl.service.socket;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.codec.string.StringDecoder;
+import io.netty.handler.codec.string.StringEncoder;
+import io.netty.util.CharsetUtil;
+
+/**
+ * 
+ * @version 1.0.0
+ * @company persagy
+ * @author zhangqiankun
+ * @date 2021年10月12日 上午10:52:37
+ */
+
+public class TestServer {
+	
+	public void start(int port) throws Exception {
+		EventLoopGroup bossGroup = new NioEventLoopGroup();
+		EventLoopGroup group = new NioEventLoopGroup();
+		try {
+			ServerBootstrap sb = new ServerBootstrap();
+			sb.option(ChannelOption.SO_BACKLOG, 1024);
+			sb.group(group, bossGroup) // 绑定线程池
+					.channel(NioServerSocketChannel.class) // 指定使用的channel
+					.localAddress(port)// 绑定监听端口
+					.childHandler(new ChannelInitializer<SocketChannel>() { // 绑定客户端连接时候触发操作
+
+						@Override
+						protected void initChannel(SocketChannel ch) throws Exception {
+							System.out.println("信息:有一客户端链接到本服务端");
+
+							ch.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8));
+							ch.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8));
+							ch.pipeline().addLast(new TestHandler()); // 客户端触发操作
+						}
+					});
+			ChannelFuture cf = sb.bind().sync(); // 服务器异步创建绑定
+			cf.channel().closeFuture().sync(); // 关闭服务器通道
+		} finally {
+			group.shutdownGracefully().sync(); // 释放线程池资源
+			bossGroup.shutdownGracefully().sync();
+		}
+	}
+
+	public static void main(String[] args) throws Exception {
+		new TestServer().start(8888); // 启动
+	}
+}

+ 30 - 38
src/main/java/com/persagy/cameractl/service/windows/NVR9MainWindows.java

@@ -1,21 +1,12 @@
 package com.persagy.cameractl.service.windows;
 
-import java.util.List;
-
 import com.persagy.cameractl.conf.NVR9Config;
-import com.persagy.cameractl.model.Channel;
 import com.persagy.cameractl.utils.Camera;
 import com.persagy.cameractl.utils.OtherTools;
 import com.persagy.cameractl.utils.ResultClass;
-import com.persagy.vsknet.structure.DECV2_PROTO_DecCircleStrateryEx;
-import com.persagy.vsknet.structure.DECV2_PROTO_HEADER_T;
-import com.persagy.vsknet.structure.DecCircleStrateryEx;
-import com.persagy.vsknet.tmp.STREAM_URI;
-import com.sun.jna.platform.win32.WinDef;
 import com.sun.jna.platform.win32.WinDef.DWORD;
 import com.sun.jna.platform.win32.WinDef.UINT;
 
-import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.util.BooleanUtil;
 import lombok.extern.slf4j.Slf4j;
 
@@ -43,7 +34,7 @@ public class NVR9MainWindows {
 	/**
 	 * 初始化及登录
 	 */
-	private ResultClass initAndLoginNVR9() {
+	/*private ResultClass initAndLoginNVR9() {
 		if (NVR9Config.vskDevNet == null)
 			return this.executeErr(false, "NVR9 SDK未初始化成功,请重启应用程序", "");
 
@@ -58,20 +49,22 @@ public class NVR9MainWindows {
 		ResultClass resultClass = new ResultClass();
 		resultClass.name = true;
 		return resultClass;
-	}
+	}*/
 
 	/**
 	 * 轮巡入口
 	 */
 	public ResultClass lunx() {
 		// 初始化
-		ResultClass initResult = this.initAndLoginNVR9();
+		/*ResultClass initResult = this.initAndLoginNVR9();
 		if (!initResult.name) {
 			return this.executeErr(false, null, "初始化失败");
 		}
 		
 		byte nweek = tagCircle.nweek;
 		int ntimeindex = tagCircle.ntimeindex;
+		int index = ntimeindex - 1;
+		int windowId = tagCircle.windowId;
 		List<Channel> channels = tagCircle.channels;
 		byte noutputscreenno = tagCircle.noutputscreenno;
 		
@@ -85,43 +78,42 @@ public class NVR9MainWindows {
 				int nChlNum = channels.size();
 				for (int j = 0; j <nChlNum; j++) {
 					Channel channel = channels.get(j);
-					int channelID = channel.getChannelID();
+					int wChanUsed = channel.getWChanUsed();
 					byte streamType = channel.getStreamType();
 
 					// 用户
-					decStratery.DecCircleStrateryChl[i][ntimeindex].ChlInfo[j].Ipclogin.username = channel.getUsername().getBytes();
-					decStratery.DecCircleStrateryChl[i][ntimeindex].ChlInfo[j].Ipclogin.password = channel.getPassword().getBytes();
+					decStratery.DecCircleStrateryChl[i][index].ChlInfo[j].Ipclogin.username = channel.getUsername().getBytes();
+					decStratery.DecCircleStrateryChl[i][index].ChlInfo[j].Ipclogin.password = channel.getPassword().getBytes();
 					
 					// 通道
-					decStratery.DecCircleStrateryChl[i][ntimeindex].ChlInfo[j].ipc.ip = channel.getIp().getBytes();
-					decStratery.DecCircleStrateryChl[i][ntimeindex].ChlInfo[j].ipc.port = channel.getPort();
-					decStratery.DecCircleStrateryChl[i][ntimeindex].ChlInfo[j].ipc.wChanUsed = channel.getWChanUsed();
+					decStratery.DecCircleStrateryChl[i][index].ChlInfo[j].ipc.ip = channel.getIp().getBytes();
+					decStratery.DecCircleStrateryChl[i][index].ChlInfo[j].ipc.port = channel.getPort();
+					decStratery.DecCircleStrateryChl[i][index].ChlInfo[j].ipc.wChanUsed = wChanUsed;
 					if (streamType == 0) {
-						STREAM_URI mediaStream = new STREAM_URI();
-						mediaStream.stream_uri = channel.getStreamUri().getBytes();
-						mediaStream.uri_size = channel.getUriSize();
-						decStratery.DecCircleStrateryChl[i][ntimeindex].ChlInfo[j].ipc.mediaStream = mediaStream;
+						decStratery.DecCircleStrateryChl[i][index].ChlInfo[j].ipc.mediaStream.stream_uri = channel.getStreamUri().getBytes();
+						decStratery.DecCircleStrateryChl[i][index].ChlInfo[j].ipc.mediaStream.uri_size = channel.getUriSize();
 					} else {
-						decStratery.DecCircleStrateryChl[i][ntimeindex].ChlInfo[j].ipc.device_addrs = channel.getDeviceAddrs().getBytes();
+						decStratery.DecCircleStrateryChl[i][index].ChlInfo[j].ipc.device_addrs = channel.getDeviceAddrs().getBytes();
 					}
 					
-					decStratery.DecCircleStrateryChl[i][ntimeindex].ChlInfo[j].ipc.frameRate = channel.getFrameRate();
-					decStratery.DecCircleStrateryChl[i][ntimeindex].ChlInfo[j].ipc.streamType = streamType;
-					decStratery.DecCircleStrateryChl[i][ntimeindex].ChlInfo[j].ipc.deviceType = 0;
-					decStratery.DecCircleStrateryChl[i][ntimeindex].ChlInfo[j].ipc.channelID = channel.getChannelID();
-					decStratery.DecCircleStrateryChl[i][ntimeindex].ChlInfo[j].ipc.manufacturer = channel.getQstrmanufactername().getBytes();
-					decStratery.DecCircleStrateryChl[i][ntimeindex].ChlInfo[j].ipc.model = channel.getQstrmodel().getBytes();
+					decStratery.DecCircleStrateryChl[i][index].ChlInfo[j].ipc.frameRate = channel.getFrameRate();
+					decStratery.DecCircleStrateryChl[i][index].ChlInfo[j].ipc.streamType = streamType;
+					decStratery.DecCircleStrateryChl[i][index].ChlInfo[j].ipc.deviceType = 0;
+					//decStratery.DecCircleStrateryChl[i][index].ChlInfo[j].ipc.channelID = channel.getChannelID();
+					//decStratery.DecCircleStrateryChl[i][index].ChlInfo[j].ipc.manufacterID = IPC_MACHINE_TYPE.ONVIF_PROTOCOL;
+					decStratery.DecCircleStrateryChl[i][index].ChlInfo[j].ipc.manufacturer = channel.getQstrmanufactername().getBytes();
+					decStratery.DecCircleStrateryChl[i][index].ChlInfo[j].ipc.model = channel.getQstrmodel().getBytes();
 					
 					// nUser
-					decStratery.DecCircleStrateryChl[i][ntimeindex].ChlInfo[j].nUser = (channelID << 16 | 0);
+					decStratery.DecCircleStrateryChl[i][index].ChlInfo[j].nUser = (wChanUsed << 16 | windowId);
 				}
 
-				decStratery.DecCircleStrateryChl[i][ntimeindex].nchanggeTime = tagCircle.nchanggeTime;
-				decStratery.DecCircleStrateryChl[i][ntimeindex].nChlNum = nChlNum;
-				decStratery.DecCircleStrateryChl[i][ntimeindex].nTimeId = ntimeindex;
-				decStratery.DecCircleStrateryChl[i][ntimeindex].nwinnum = tagCircle.nwndnumber;
-				decStratery.DecCircleStrateryChl[i][ntimeindex].uistarttime = tagCircle.ulstarttime;
-				decStratery.DecCircleStrateryChl[i][ntimeindex].uiendtime = tagCircle.ulendtime;
+				decStratery.DecCircleStrateryChl[i][index].nchanggeTime = tagCircle.nchanggeTime;
+				decStratery.DecCircleStrateryChl[i][index].nChlNum = nChlNum;
+				decStratery.DecCircleStrateryChl[i][index].nTimeId = ntimeindex;
+				decStratery.DecCircleStrateryChl[i][index].nwinnum = tagCircle.nwndnumber;
+				decStratery.DecCircleStrateryChl[i][index].uistarttime = tagCircle.ulstarttime;
+				decStratery.DecCircleStrateryChl[i][index].uiendtime = tagCircle.ulendtime;
 			}
 		}
 		
@@ -140,8 +132,8 @@ public class NVR9MainWindows {
 				new WinDef.UINT(decStratery.size()));
 		if (!BooleanUtil.isTrue(setResult)) {
 			int errCode = NVR9Config.getErrCode();
-			return this.executeErr(true, "设置视频源轮巡失败,错误码:" + errCode + ",窗口号:" + tagCircle.noutputscreenno, "");
-		}
+			return this.executeErr(true, "设置视频源轮巡策略失败,错误码:" + errCode + ",窗口号:" + tagCircle.noutputscreenno, "");
+		}*/
 		
 		return this.executeSuccess(null);
 	}

+ 264 - 0
src/main/java/com/persagy/cameractl/service/windows/Nvr9CameraExecuteApi.java

@@ -0,0 +1,264 @@
+package com.persagy.cameractl.service.windows;
+
+import java.util.List;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.persagy.cameractl.cache.SceneConfigCache;
+import com.persagy.cameractl.conf.NVR9Config;
+import com.persagy.cameractl.model.Camera;
+import com.persagy.cameractl.model.Channel;
+import com.persagy.cameractl.model.NvrSceneRelation;
+import com.persagy.cameractl.model.SceneConfig;
+import com.persagy.cameractl.utils.OtherTools;
+import com.persagy.cameractl.utils.ResultClass;
+import com.persagy.vsknet.enums.IPC_MACHINE_TYPE;
+import com.persagy.vsknet.structure.CurShowDevEx;
+import com.persagy.vsknet.structure.DECV2_PROTO_HEADER_T;
+import com.persagy.vsknet.structure.DECV2_PROTO_IPC;
+import com.persagy.vsknet.structure.IPC_ArrayEx;
+import com.persagy.vsknet.tmp.AUTHORIZATION;
+import com.persagy.vsknet.tmp.SERVER_URI;
+import com.sun.jna.platform.win32.WinDef;
+import com.sun.jna.platform.win32.WinDef.UINT;
+
+import cn.hutool.core.util.BooleanUtil;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 
+ * @version 1.0.0
+ * @company persagy 
+ * @author zhangqiankun
+ * @date 2021年10月12日 上午11:03:13
+ */
+@Slf4j
+public class Nvr9CameraExecuteApi {
+
+	/** 当前登陆者 */
+	private UINT login_id;
+
+	public ResultClass toup(SceneConfig sceneConfig) {
+		// 设置通道预览
+		List<Channel> channels = sceneConfig.getChannels();
+		for (Channel channel : channels) {
+			int windowId = channel.getWindowId();
+			
+			// 登录
+			NvrSceneRelation relation = SceneConfigCache.SCENE_CONFIG.get(SceneConfigCache.getCacheKey(sceneConfig.getGroup(), sceneConfig.getScene(), windowId));
+			byte sdkChannelId = relation.getSdkChannelId();
+			boolean initAndLoginNVR9 = this.login(relation);
+			if (!initAndLoginNVR9) {
+				return this.executeErr(false, null, "登录失败");
+			}
+			
+			int errCode = -1;
+			Boolean result = null;
+			List<Camera> cameras = channel.getCameras();
+			for (int i = 0; i < cameras.size(); i++) {
+				errCode = -1;
+				result = null;
+				
+				Camera camera = cameras.get(i);
+				int wChanUsed = camera.getWChanUsed();
+				byte streamType = camera.getStreamType();
+
+				// 用户
+				IPC_ArrayEx IPC_ArrayEx = new IPC_ArrayEx();
+				IPC_ArrayEx.Ipclogin.username = camera.getUsername().getBytes();
+				IPC_ArrayEx.Ipclogin.password = camera.getPassword().getBytes();
+				
+				// 通道
+				IPC_ArrayEx.ipc.ip = camera.getIp().getBytes();
+				IPC_ArrayEx.ipc.port = camera.getPort();
+				IPC_ArrayEx.ipc.wChanUsed = wChanUsed;
+				if (streamType == 0) {
+					IPC_ArrayEx.ipc.mediaStream.stream_uri = camera.getStreamUri().getBytes();
+					IPC_ArrayEx.ipc.mediaStream.uri_size = camera.getUriSize();
+				} else {
+					IPC_ArrayEx.ipc.device_addrs = camera.getDeviceAddrs().getBytes();
+				}
+				
+				IPC_ArrayEx.ipc.frameRate = camera.getFrameRate();
+				IPC_ArrayEx.ipc.streamType = streamType;
+				IPC_ArrayEx.ipc.deviceType = 0;
+				//IPC_ArrayEx.ipc.channelID = channel.getChannelID();
+				IPC_ArrayEx.ipc.manufacterID = new IPC_MACHINE_TYPE();
+				IPC_ArrayEx.ipc.manufacturer = camera.getManufactername().getBytes();
+				IPC_ArrayEx.ipc.model = camera.getModel().getBytes();
+				
+				// nUser
+				IPC_ArrayEx.nUser = (wChanUsed << 16) + windowId;	// 添加到的通道号与窗口号
+				
+				// 删除通道预览
+				DECV2_PROTO_IPC deletePreview = new DECV2_PROTO_IPC();
+				deletePreview.proHead.master = sdkChannelId;		//输出屏号从0开始
+				deletePreview.proHead.dataLen = IPC_ArrayEx.size();
+				result = NVR9Config.vskDevNet.NS_NET_SetNVRConfig(login_id, new WinDef.UINT(142), new WinDef.LONG(1), 
+						deletePreview.getPointer(), new WinDef.UINT(deletePreview.size()));
+				if (BooleanUtil.isTrue(result)) {
+					// 添加通道预览
+					DECV2_PROTO_IPC updatePreview = new DECV2_PROTO_IPC();
+					updatePreview.proHead.master = sdkChannelId;	//输出屏号从0开始
+					updatePreview.proHead.dataLen = IPC_ArrayEx.size();
+					result = NVR9Config.vskDevNet.NS_NET_SetNVRConfig(login_id, new WinDef.UINT(141), new WinDef.LONG(1), 
+							deletePreview.getPointer(), new WinDef.UINT(deletePreview.size()));
+					if (!BooleanUtil.isTrue(result)) {
+						errCode = NVR9Config.getErrCode();
+						log.warn("添加通道预览失败,错误码: " + errCode);
+					}
+				} else {
+					errCode = NVR9Config.getErrCode();
+					log.warn("删除通道预览失败,错误码: " + errCode);
+				}
+			}
+			
+			// 退出登录
+			this.logout(true);
+			if (!BooleanUtil.isTrue(result)) {
+				return this.executeErr(true, errCode + "", "投屏操作失败");
+			}
+		}
+		
+		return this.executeSuccess(false, "添加通道预览配置成功");
+	}
+
+	
+	/**
+	 * 获取解码器当前预览视频信息
+	 * 
+	 * @param relation
+	 * @date 2021年10月21日 下午5:33:22
+	 */
+	public ResultClass currentVideo(NvrSceneRelation relation) {
+		byte sdkChannelId = relation.getSdkChannelId();
+		if (login_id == null) {
+			boolean initAndLoginNVR9 = this.login(relation);
+			if (!initAndLoginNVR9) {
+				return this.executeErr(false, null, "登录失败");
+			}
+		}
+		
+		DECV2_PROTO_HEADER_T DECV2_PROTO_HEADER_T = new DECV2_PROTO_HEADER_T();
+		DECV2_PROTO_HEADER_T.master = sdkChannelId;
+		
+		CurShowDevEx curShowDevEx = new CurShowDevEx();
+		Boolean result = NVR9Config.vskDevNet.NS_NET_GetNVRConfigEx(login_id, new WinDef.UINT(136), new WinDef.LONG(0), 
+				DECV2_PROTO_HEADER_T.getPointer(), new WinDef.UINT(DECV2_PROTO_HEADER_T.size()), curShowDevEx.getPointer(), 
+				new WinDef.UINT(curShowDevEx.size()), null);
+		if (!BooleanUtil.isTrue(result)) {
+			int errCode = NVR9Config.getErrCode();
+			log.warn("获取解码器当前预览视频信息,错误码: " + errCode);
+		}
+		
+		JSONObject jsonObject = new JSONObject();
+		jsonObject.put("ChlNum", curShowDevEx.ChlNum);
+		jsonObject.put("WindowsNum", curShowDevEx.WindowsNum);
+		
+		IPC_ArrayEx[] arrayExs = curShowDevEx.ChlInfo;
+		if (arrayExs != null) {
+			JSONArray chlInfos = new JSONArray();
+			for (int i = 0; i < arrayExs.length; i++) {
+				JSONObject chlInfo = new JSONObject();
+				IPC_ArrayEx ipc_ArrayEx = arrayExs[0];
+				
+				SERVER_URI server_URI = ipc_ArrayEx.ipc;
+				JSONObject ipc = new JSONObject();
+				
+				ipc.put("wChanUsed", server_URI.wChanUsed);
+				ipc.put("device_addrs", new String(server_URI.device_addrs));
+				
+				JSONObject mediaStream = new JSONObject();
+				mediaStream.put("stream_uri", new String(server_URI.mediaStream.stream_uri));
+				mediaStream.put("uri_size", server_URI.mediaStream.uri_size);
+				ipc.put("mediaStream", mediaStream);
+				
+				ipc.put("addrs_size", server_URI.addrs_size);
+				ipc.put("ip", server_URI.wChanUsed);
+				ipc.put("wChanUsed", new String(server_URI.ip));
+				ipc.put("port", server_URI.port);
+				ipc.put("manufacterID", server_URI.manufacterID);
+				ipc.put("frameRate", server_URI.frameRate);
+				ipc.put("streamType", server_URI.streamType);
+				ipc.put("deviceType", server_URI.deviceType);
+				ipc.put("channelID", server_URI.channelID);
+				ipc.put("manufacturer", new String(server_URI.manufacturer));
+				ipc.put("model", new String(server_URI.model));
+				
+				AUTHORIZATION authorization = ipc_ArrayEx.Ipclogin;
+				JSONObject Ipclogin = new JSONObject();
+				Ipclogin.put("username", new String(authorization.username));
+				Ipclogin.put("password", new String(authorization.password));
+				
+				chlInfo.put("ipc", ipc);
+				chlInfo.put("Ipclogin", Ipclogin);
+				chlInfo.put("nUser", ipc_ArrayEx.nUser);
+			}
+			
+			jsonObject.put("ChlInfo", chlInfos);
+		}
+		return this.executeSuccess(true, jsonObject);
+	}
+	
+	
+	/**
+	 * 初始化及登录
+	 */
+	private boolean login(NvrSceneRelation relation) {
+		if (NVR9Config.vskDevNet == null) {
+			log.warn("NVR9 SDK未初始化成功,请重启应用程序");
+			return false;
+		}
+
+		// 登录
+		login_id = NVR9Config.vskDevNet.NS_NET_Login(relation.getIp().getBytes(), new WinDef.WORD(relation.getPort()),
+				relation.getUsername().getBytes(), relation.getPassword().getBytes(), null, null);
+		long userId = login_id == null ? -1L : login_id.longValue();
+		if (userId < 0) {
+			log.warn("登录失败,错误码:{}", NVR9Config.getErrCode());
+			return false;
+		}
+
+		return true;
+	}
+	
+	/**
+	 * 返回错误结果,同时释放资源 isLogingout 是否注销登录,在注册失败时只需要释放SDK不需要注销登录 errCode
+	 * 调用sdk的结果,一般为错误码 errPrefix 返回给客户端调用者的错误信息前缀
+	 * 
+	 * @param isLogingout
+	 * @param errCode
+	 * @param errPrefix
+	 * @return
+	 * @date 2021年9月30日 上午11:23:43
+	 */
+	private ResultClass executeErr(boolean isLogingout, String errCode, String errPrefix) {
+		this.logout(isLogingout);
+		String errReason = OtherTools.joinErrStr(errCode, errPrefix);
+		return OtherTools.executeErr(errReason);
+	}
+
+	/**
+	 * 返回调用成功及结果数据,同时注销登陆
+	 */
+	private ResultClass executeSuccess(boolean isLogingout, Object resultData) {
+		this.logout(isLogingout);
+		return OtherTools.executeSuccess(resultData);
+	}
+
+	/**
+	 * 注销登录 isLogingout 是否注销登录,在注册失败时只需要释放SDK不需要注销登录
+	 */
+	private void logout(boolean isLogingout) {
+		// 注销设备,即注销登录
+		if (isLogingout) {
+			Boolean logout = NVR9Config.vskDevNet.NS_NET_Logout(this.login_id);
+			if (BooleanUtil.isTrue(logout)) {
+				log.info("NVR9000 SDK 注销成功");
+			} else {
+				log.error("NVR9000 SDK 注销失败");
+			}
+		}
+	}
+	
+}

+ 20 - 0
src/main/java/com/persagy/vsknet/structure/CurShowDevEx.java

@@ -0,0 +1,20 @@
+package com.persagy.vsknet.structure;
+
+import com.sun.jna.Structure;
+
+/**
+ * 
+ * @version 1.0.0
+ * @company persagy 
+ * @author zhangqiankun
+ * @date 2021年10月21日 下午6:30:14
+ */
+public class CurShowDevEx extends Structure {
+
+	public int WindowsNum;
+	
+	public int ChlNum;
+	
+	public IPC_ArrayEx[] ChlInfo = new IPC_ArrayEx[16];		//通道信息。
+	
+}

+ 18 - 0
src/main/java/com/persagy/vsknet/structure/DECV2_PROTO_IPC.java

@@ -0,0 +1,18 @@
+package com.persagy.vsknet.structure;
+
+import com.sun.jna.Structure;
+
+/**
+ * 
+ * @version 1.0.0
+ * @company persagy
+ * @author zhangqiankun
+ * @date 2021年10月13日 下午2:37:41
+ */
+public class DECV2_PROTO_IPC extends Structure {
+
+	public DECV2_PROTO_HEADER_T proHead;
+	
+	public IPC_ArrayEx ipc;
+	
+}

+ 19 - 0
src/main/java/com/persagy/vsknet/structure/DECV2_PROTO_SPLIT_PREVIEW.java

@@ -0,0 +1,19 @@
+package com.persagy.vsknet.structure;
+
+import com.sun.jna.Structure;
+
+/**
+ * 
+ * @version 1.0.0
+ * @company persagy
+ * @author zhangqiankun
+ * @date 2021年10月13日 下午10:13:55
+ */
+
+public class DECV2_PROTO_SPLIT_PREVIEW extends Structure {
+
+	public DECV2_PROTO_HEADER_T proHead;
+
+	public TagChangeWndStyle ipc;
+
+}

+ 25 - 0
src/main/java/com/persagy/vsknet/structure/TagChangeWndStyle.java

@@ -0,0 +1,25 @@
+package com.persagy.vsknet.structure;
+
+/**
+ * 
+ * @version 1.0.0
+ * @company persagy 
+ * @author zhangqiankun
+ * @date 2021年10月13日 下午10:16:52
+ */
+
+public class TagChangeWndStyle {
+
+	/** 画面分割数,值包括 1、4、9、16 */
+	public int nWndNum;
+	
+	/**
+	 * 当 nWndNum 为1时,nChl 可以为0,1,2,3....63,每个代表 1画面 显示原来的配置的通道号。
+	 * 当 nWndNum 为4时,nChl 可以使64, 65、66、...79,其中 64 表示显示 1~4通道,65表示显示 5~8 通道,以此类推。
+	 * 当 nWndNum 为9时,nChl可使用 80,81,82…87,80表示 1~9通道,81 表示 8~16,82表示 17~25通道,83 表示 24~32依次类推。
+	 * 当 nWndNum 为16时,nChl 可使用值 88、89、90、91,88表示1~16通道,89表示 17~32通道,以此类推。当 nWndNum 为2时,nChl可使用92~124,
+	 * 		其中92 表示1~2通道,93表示3~4通道,以此类推。
+	 */
+	public int nChl;
+	
+}

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 1 - 1
src/main/resources/application.yml