Przeglądaj źródła

云台控制&视频回放

ZhangWenTao 2 lat temu
rodzic
commit
2d78f44560

+ 1 - 0
.gitignore

@@ -3,6 +3,7 @@
 *.iml
 out
 gen
+tempVideo/
 
 # Visual Studio Code
 .history/

+ 115 - 0
src/main/java/com/persagy/cameractl/common/VideoExportProcessContext.java

@@ -0,0 +1,115 @@
+package com.persagy.cameractl.common;
+
+import com.sun.jna.Pointer;
+import lombok.Getter;
+import lombok.SneakyThrows;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.file.Files;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
+
+/**
+ * @author : ZhangWenTao
+ * @version : 1.0
+ * @since : 2022/6/21 14:58
+ */
+public class VideoExportProcessContext {
+
+    // 同时只能有一个视频在传输中, 这里将它记录下来
+    private static volatile VideoExportProcessContext CURRENT_CONTEXT;
+
+    private static final Semaphore semaphore = new Semaphore(1);
+
+    @Getter
+    private final String pUser;
+
+    @Getter
+    private final String filePath;
+
+    @Getter
+    private final String tempFilePath;
+
+    private final CountDownLatch latch = new CountDownLatch(1);
+
+    private final RandomAccessFile randomFile;
+
+    private volatile boolean success;
+
+    private volatile boolean written;
+
+    private volatile boolean inTransmission;
+
+    @SneakyThrows
+    public VideoExportProcessContext(Pointer pUser, String filePath, String tempFilePath) {
+        this.pUser = pUser.getString(0);
+        this.filePath = filePath;
+        this.tempFilePath = tempFilePath;
+        this.randomFile = new RandomAccessFile(tempFilePath, "rw");
+    }
+
+    public synchronized void complete(boolean success) {
+        if (latch.getCount() < 1) {
+            throw new IllegalStateException("process completed !");
+        }
+
+        VideoExportProcessContext.this.success = success;
+        latch.countDown();
+    }
+
+    public synchronized void write(byte[] buf) throws IOException {
+        if (latch.getCount() < 1) {
+            throw new IllegalStateException("process completed !");
+        }
+
+        randomFile.write(buf);
+        written = true;
+    }
+
+    @SneakyThrows
+    public boolean await() {
+        latch.await();
+        return success;
+    }
+
+    public boolean hasData() {
+        return written;
+    }
+
+    public synchronized void clean() {
+        try {
+            randomFile.close();
+            Files.delete(new File(tempFilePath).toPath());
+        } catch (IOException e) {
+            // ignore
+        }
+
+        endTransmission();
+    }
+
+    public synchronized void startTransmission() throws InterruptedException {
+        if (inTransmission) {
+            return;
+        }
+
+        semaphore.acquire();
+        inTransmission = true;
+        CURRENT_CONTEXT = this;
+    }
+
+    public synchronized void endTransmission() {
+        if (inTransmission) {
+            semaphore.release();
+            inTransmission = false;
+            CURRENT_CONTEXT = null;
+        }
+    }
+
+    public static VideoExportProcessContext getCurrentContext() {
+        return CURRENT_CONTEXT;
+    }
+
+
+}

+ 0 - 15
src/main/java/com/persagy/cameractl/conf/AllStaticConfig.java

@@ -17,24 +17,9 @@ public class AllStaticConfig {
 	// 老版的回放视频数据的回调
 	public static DataPlayCallBackClass dataPlayCallBackClass = new DataPlayCallBackClass();
 
-	// 回放视频结束的回调
-	public static EndPlayCallBackClass endPlayCallBackClass = new EndPlayCallBackClass();
-
 	// 新版的回放视频数据的回放
 	public static VideoNoViskHeadFrameCallBackClass videoNoViskHeadFrameCallBackClass = new VideoNoViskHeadFrameCallBackClass();
 
-	// 存放回放视频数据的集合
-	public static List<byte[]> backByteBufferList = new ArrayList<byte[]>();
-
-	// 回放mp4生成的状态码,0 生成中 1 生成结束 2生成错误
-	public static int playBackMp4State = 1;
-
-	// 回放时生成的回放文件名称
-	public static String playBackFileName;
-
-	// 回放时生成的回放文件路径,包含文件名称
-	public static String playBackFilePath;
-	
 	public static String zhaoshangMesHost;
 
 	public static String zhaoshangApiIp;

+ 94 - 126
src/main/java/com/persagy/cameractl/service/windows/ZhaosMainWindows.java

@@ -1,41 +1,32 @@
 package com.persagy.cameractl.service.windows;
 
-import java.io.File;
-import java.io.FileOutputStream;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-
+import com.persagy.cameractl.common.VideoExportProcessContext;
 import com.persagy.cameractl.conf.AllStaticConfig;
-import com.persagy.cameractl.utils.Camera;
-import com.persagy.cameractl.utils.DateUtil;
-import com.persagy.cameractl.utils.EnumTools;
-import com.persagy.cameractl.utils.JsonTools;
-import com.persagy.cameractl.utils.OtherTools;
-import com.persagy.cameractl.utils.ResultClass;
-import com.persagy.cameractl.utils.StringTools;
+import com.persagy.cameractl.utils.*;
+import com.persagy.nvr.EndPlayCallBackClass;
 import com.sun.jna.Memory;
 import com.sun.jna.Pointer;
 import com.sun.jna.platform.win32.WinDef;
-import com.sun.jna.platform.win32.WinDef.CHARByReference;
-import com.sun.jna.platform.win32.WinDef.DWORD;
-import com.sun.jna.platform.win32.WinDef.DWORDByReference;
-import com.sun.jna.platform.win32.WinDef.UINT;
-import com.sun.jna.platform.win32.WinDef.UINTByReference;
-
+import com.sun.jna.platform.win32.WinDef.*;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
 import sun.misc.BASE64Encoder;
 
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.HashMap;
+import java.util.Map;
+
 @Slf4j
 @SuppressWarnings("restriction")
 public class ZhaosMainWindows {
-	
+
 	// 默认连接超时时间,单位:毫秒
 	int wait_time = 5000;
 
 	// 摄像头对象
 	Camera _camera;
-	
+
 	// 登录句柄
 	DWORD loginHandler;
 
@@ -83,19 +74,19 @@ public class ZhaosMainWindows {
 
 	/**
 	 * 登录
-	 * 
+	 *
 	 * @return
 	 * @date 2021年9月28日 上午10:32:05
 	 */
 	private String login() {
 		try {
-			Map<String, String> userinfoMap = new HashMap<String, String>();
+			Map<String, String> userinfoMap = new HashMap<>();
 			String userNameBase64 = new BASE64Encoder().encode(_camera.userName.getBytes());
 			String passwordBase64 = new BASE64Encoder().encode(_camera.password.getBytes());
 			userinfoMap.put("user_name", userNameBase64);
 			userinfoMap.put("password", passwordBase64);
 
-			Map<String, Object> loginInfoMap = new HashMap<String, Object>();
+			Map<String, Object> loginInfoMap = new HashMap<>();
 			loginInfoMap.put("user_info", userinfoMap);
 			loginInfoMap.put("strNvrIp", _camera.cameraIp);
 			loginInfoMap.put("nNvrPort", _camera.cameraPort);
@@ -130,7 +121,7 @@ public class ZhaosMainWindows {
 
 		// 登录
 		String loginResult = this.login();
-		if (loginResult != "true") {
+		if (!"true".equals(loginResult)) {
 			return this.executeErr(false, loginResult, "登录失败");
 		}
 
@@ -138,7 +129,7 @@ public class ZhaosMainWindows {
 		resultClass.name = true;
 		return resultClass;
 	}
-	
+
 	/**
 	 * 获得控制编码
 	 */
@@ -165,8 +156,8 @@ public class ZhaosMainWindows {
 			if (controlResult != 0) {
 				return String.valueOf(controlResult);
 			}
-			// 休眠1秒,即让云台执行1秒后再进行停止
-			Thread.sleep(1000);
+			// 休眠,即让云台执行后再进行停止
+			Thread.sleep(200);
 			// 再执行停止命令
 			reqPtzControlMap.put("value", 0);
 			String reqPtzControlStr2 = JsonTools.obj2Str(reqPtzControlMap);
@@ -174,7 +165,7 @@ public class ZhaosMainWindows {
 			// byte[] reqPtzControlBytes2 = reqPtzControlStr2.getBytes();
 			int controlResult2 = AllStaticConfig.vskClient.JsonSdk_PtzControl(this.loginHandler, reqPtzControlStr2);
 			if (controlResult2 != 0) {
-				return String.valueOf("控制停止时失败:" + controlResult2);
+				return "控制停止时失败:" + controlResult2;
 			}
 
 			return "true";
@@ -187,65 +178,44 @@ public class ZhaosMainWindows {
 	/**
 	 * 视频回放
 	 */
-	private String startPlayBack() {
+	private String startPlayBack(WinDef.LPVOID pUser, EndPlayCallBackClass endPlayCallBackClass,
+								 VideoExportProcessContext context) {
 		try {
-			Map<String, String> userinfoMap = new HashMap<String, String>();
-			String userNameBase64 = new BASE64Encoder().encode(_camera.userName.getBytes());
-			String passwordBase64 = new BASE64Encoder().encode(_camera.password.getBytes());
-			userinfoMap.put("user_name", userNameBase64);
-			userinfoMap.put("password", passwordBase64);
-
 			// 公元1970年1月1日0时0分0 秒算起至今的UTC时间所经过的秒数。
-			long startTimeSeconds = new DateUtil(_camera.startDateStr).getSecondsStart1970UTC();
-			long endTimeSeconds = new DateUtil(_camera.endDateStr).getSecondsStart1970UTC();
+			long startTimeSeconds = new DateUtil(_camera.startDateStr).getSecondsStart1970UTC8();
+			long endTimeSeconds = new DateUtil(_camera.endDateStr).getSecondsStart1970UTC8();
 
-			Map<String, Object> playbackStreamTimeMap = new HashMap<String, Object>();
+			Map<String, Object> playbackStreamTimeMap = new HashMap<>();
 			playbackStreamTimeMap.put("chid", _camera.channel - 1);
-			playbackStreamTimeMap.put("stream_type", 0);
+			playbackStreamTimeMap.put("stream_type", _camera.streamType);
 			playbackStreamTimeMap.put("start_time", startTimeSeconds);
 			playbackStreamTimeMap.put("end_time", endTimeSeconds);
-			playbackStreamTimeMap.put("user_info", userinfoMap);
 
 			String pInfoStr = JsonTools.obj2Str(playbackStreamTimeMap);
-			// byte[] pInfo = pInfoStr.getBytes();
-
-			// int pUserValue = 2;
-			// pUserValue.getpo
-			String pUserValue = StringTools.getUUID();
-			Pointer pUserPointer = new Memory(40);
-			pUserPointer.setString(0, pUserValue);
-			WinDef.LPVOID pUser = new WinDef.LPVOID(pUserPointer);
-
-//			ULONGLONG pTotalSizeLong = new ULONGLONG(1);
-//			ULONGLONGByReference pTotalSize = new ULONGLONGByReference(pTotalSizeLong);
-
-			// DWORD playBackIdDWORD = new DWORD(Native.getNativeSize(DWORD.class));
-//			DWORD playBackIdDWORD = new DWORD(96);
-//			WinDef.DWORDByReference pnPlaybackID = new WinDef.DWORDByReference(playBackIdDWORD);
 
-			// Pointer pnPlaybackID = new Memory(20);
-
-			// if (_camera.isUseCustomCall == 1) {
-			//
-			// }
-
-			log.error("-----------即将调用JsonSdk_PlayBackStartByTime,串:" + pInfoStr);
-			log.error("loginHandler:" + loginHandler.intValue());
-			log.error("startTimeSeconds:" + startTimeSeconds + ",endTimeSeconds:" + endTimeSeconds);
+			log.info("-----------即将调用JsonSdk_PlayBackStartByTime,串:" + pInfoStr);
+			log.info("loginHandler:" + loginHandler.intValue());
+			log.info("startTimeSeconds:" + startTimeSeconds + ",endTimeSeconds:" + endTimeSeconds);
 			// 发送根据时间进行回放的指令
-			// int playBackResult =
-			// AllStaticConfig.vskClient.JsonSdk_PlayBackStartByTime(loginHandler, pInfoStr,
-			// new DataPlayCallBackClass(), new EndPlayCallBackClass(), null, pUser,
-			// pTotalSize, pnPlaybackID);
+			WinDef.DWORDLONG pTotalSize = new WinDef.DWORDLONG();
+			WinDef.DWORDByReference pnDownID = new WinDef.DWORDByReference(new WinDef.DWORD(0));
+			context.startTransmission();
+
 			int playBackResult = AllStaticConfig.vskClient.JsonSdk_PlayBackStartByTime(loginHandler, pInfoStr,
-					AllStaticConfig.dataPlayCallBackClass, AllStaticConfig.endPlayCallBackClass, null, pUser, null,
-					null);
+					AllStaticConfig.dataPlayCallBackClass, endPlayCallBackClass, null, pUser,
+					pTotalSize, pnDownID);
+
+//			WinDef.DWORDByReference pTotalSize = new WinDef.DWORDByReference();
+//			WinDef.DWORDByReference pnDownID = new WinDef.DWORDByReference(new WinDef.DWORD(0));
+//			int playBackResult = AllStaticConfig.vskClient.JsonSdk_DownloadStartByTime(loginHandler, pInfoStr,
+//					AllStaticConfig.dataPlayCallBackClass, AllStaticConfig.endPlayCallBackClass, "c:/persagy-camera/persagy-camera/download.h264",
+//					pUser, 0, pTotalSize, pnDownID);
 
 			if (playBackResult != 0) {
+				context.endTransmission();
 				return String.valueOf(playBackResult);
 			}
-//			long pnPlaybackIdValue = pnPlaybackID.getValue().longValue();
-			AllStaticConfig.playBackMp4State = 0;
+
 			return "true";
 		} catch (Exception e) {
 			log.error("调用回放时异常:", e);
@@ -257,8 +227,8 @@ public class ZhaosMainWindows {
 	private String searchLog() {
 		try {
 			// 公元1970年1月1日0时0分0 秒算起至今的UTC时间所经过的秒数。
-			long startTimeMillSeconds = new DateUtil(_camera.startDateStr).getSecondsStart1970UTC();
-			long endTimeMillSeconds = new DateUtil(_camera.endDateStr).getSecondsStart1970UTC();
+			long startTimeMillSeconds = new DateUtil(_camera.startDateStr).getSecondsStart1970UTC8();
+			long endTimeMillSeconds = new DateUtil(_camera.endDateStr).getSecondsStart1970UTC8();
 
 			Map<String, Object> sysLogMap = new HashMap<String, Object>();
 			sysLogMap.put("log_type", 1);
@@ -330,7 +300,7 @@ public class ZhaosMainWindows {
 			return initResultClass;
 
 		String controlResult = onPtzDirctCtrl();
-		if (controlResult != "true") {
+		if (!"true".equals(controlResult)) {
 			return this.executeErr(true, controlResult, "控制指令调用失败");
 		}
 		return this.executeSuccess(null);
@@ -339,65 +309,63 @@ public class ZhaosMainWindows {
 	/**
 	 * 回放入口 synchronized关键字即把方法改为同步,两个线程同时调用该方法时,上一个线程执行完后下一个线程才会执行
 	 */
-	public synchronized ResultClass playBackMain() {
-		try {
-			ResultClass returnResult = new ResultClass();
-			AllStaticConfig.playBackFilePath = OtherTools.getVideoFilePath();
-			if (AllStaticConfig.playBackFilePath.equals("")) {
-				returnResult.name = false;
-				returnResult.reason = "回放文件名称生成失败";
-				return returnResult;
-			}
+	public ResultClass playBackMain() {
+		ResultClass returnResult = new ResultClass();
+		String playBackFilePath = OtherTools.getVideoFilePath();
+		if (StringUtils.isEmpty(playBackFilePath)) {
+			returnResult.name = false;
+			returnResult.reason = "回放文件名称生成失败";
+			return returnResult;
+		}
 
-			File mp4File = new File(AllStaticConfig.playBackFilePath);
-			AllStaticConfig.playBackFileName = mp4File.getName();
+		File mp4File = new File(playBackFilePath);
+		String playBackFileName = mp4File.getName();
 
-			String errPrefixStr = "回放失败";
-			// 初始化
-			ResultClass initResultClass = initAndLogin();
-			if (!initResultClass.name)
-				return initResultClass;
+		String tempPlayBackFilePath = playBackFilePath + ".tmp";
+		String token = OtherTools.getMp4NamePrefix(playBackFileName);
 
+		String errPrefixStr = "回放失败";
+		// 初始化
+		ResultClass initResultClass = initAndLogin();
+		if (!initResultClass.name)
+			return initResultClass;
+
+		String pUserValue = StringTools.getUUID();
+		Pointer pUserPointer = new Memory(40);
+		pUserPointer.setString(0, pUserValue);
+		WinDef.LPVOID pUser = new WinDef.LPVOID(pUserPointer);
+
+		VideoExportProcessContext context = new VideoExportProcessContext(
+				pUserPointer, playBackFilePath, tempPlayBackFilePath);
+
+		// 保证在当前流程中不被GC回收掉
+		EndPlayCallBackClass endPlayCallBackClass = new EndPlayCallBackClass(context);
+
+		try {
 			// 回放开始
-			String playBackResult = this.startPlayBack();
-			if (playBackResult != "true")
+			String playBackResult = this.startPlayBack(pUser, endPlayCallBackClass, context);
+			if (!"true".equals(playBackResult))
 				return this.executeErr(true, playBackResult, "执行回放时失败");
 
-			final CountDownLatch latch = new CountDownLatch(1);// 使用java并发库concurrent
-			new Thread(new Runnable() {
-				public void run() {
-					// 生成中
-					while (AllStaticConfig.playBackMp4State == 0) {
-						try {
-							Thread.sleep(1);
-						} catch (Exception e) {
-						}
-					}
-					latch.countDown();// 让latch中的数值减一
-
-				}
-			}).start();
-			try {
-				latch.await();// 阻塞当前线程直到latch中数值为零才执行
-				// 生成成功
-				if (AllStaticConfig.playBackMp4State == 1) {
-					log.info("正在拼接正常url");
-					String token = OtherTools.getMp4NamePrefix(AllStaticConfig.playBackFileName);
-					String url = OtherTools.playMp4RootUrl + token;
-					Map<String, String> dataMap = new HashMap<String, String>();
-					dataMap.put("url", url);
-					log.info("即将正常返回url");
-					return this.executeSuccess(dataMap);
+			// 生成成功
+			if (context.await()) {
+				if (!context.hasData()) {
+					return this.executeErr(true, "无可回放内容", errPrefixStr);
 				}
-				log.info("errPrefixStr:" + errPrefixStr);
-				return this.executeErr(true, "视频文件生成失败", errPrefixStr);
-			} catch (Exception e) {
-				log.error("回放异常:", e);
-				return this.executeErr(true, "等待MP4生成异常", errPrefixStr);
+
+				String url = OtherTools.playMp4RootUrl + token;
+				Map<String, String> dataMap = new HashMap<>();
+				dataMap.put("url", url);
+				return this.executeSuccess(dataMap);
 			}
+
+			log.info("errPrefixStr:" + errPrefixStr);
+			return this.executeErr(true, "视频文件生成失败", errPrefixStr);
 		} catch (Exception e) {
-			log.error("回放入口异常:", e);
-			return this.executeErr(true, "回放入口异常", "");
+			log.error("回放异常:", e);
+			return this.executeErr(true, "视频文件生成异常", errPrefixStr);
+		} finally {
+			context.clean();
 		}
 
 	};

+ 15 - 8
src/main/java/com/persagy/cameractl/utils/Camera.java

@@ -4,31 +4,40 @@ import java.util.List;
 
 import com.persagy.cameractl.model.Channel;
 
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+
 /**
  * 摄像头对象
  * */
 public class Camera {
 	
 	// 通道号 1~32表示模拟通道,9000系列混合型DVR和NVR等设备的IP通道从33开始(即程序用的通道号为33+通道号-1)。回放、实时播放、控制使用
+	@Min(1)
+	@Max(256)
 	public int channel = 1;
 
-	// 码流类型 0:主码流 1:子码流 2:第三码流 参数不填,默认为码流
-	public int streamType = 1;
+	// 码流类型 0:主码流 1:子码流 2:第三码流 参数不填,默认为码流
+	public int streamType = 0;
 
-	// 命令类型,参见EnumTool.listSdkCommand
+	/**
+	 * 	命令类型,参见 {@link EnumTools#listSdkCommand}
+	 */
 	public String command;
 
 	// 开始还是停止 true 开始 false 停止
 	public Boolean dwStop = true;
 
-	// 调用硬件SDK时值范围1~100,默认50。指上仰下俯的速度、左转右转的速度、左上左下右上右下的水平速度
-	public int speed = 50;
+	// 调用硬件SDK时值范围1~100,默认25。指上仰下俯的速度、左转右转的速度、左上左下右上右下的水平速度
+	@Min(1)
+	@Max(100)
+	public int speed = 25;
 
 	// 摄像头IP
 	public String cameraIp;
 
 	// 摄像头服务端口号
-	public int cameraPort = 8000;
+	public int cameraPort = 7000;
 
 	// 登录用户名
 	public String userName = "admin";
@@ -80,8 +89,6 @@ public class Camera {
 	// 8900平台接口所需要的参数
 	public String jsonParam;
 	
-	public int isUseCustomCall=0;
-	
 	public CameraLoop[] cameraLoopArr;
 	
 	/** 轮巡时间 */

+ 5 - 5
src/main/java/com/persagy/cameractl/utils/DateUtil.java

@@ -11,21 +11,21 @@ public class DateUtil {
 	public DateUtil(String dateStr) {
 		dateStr = dateStr.replace(' ', 'T');
 		LocalDateTime localDateTime = LocalDateTime.parse(dateStr);
-		ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, ZoneId.of("UTC"));
+		ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, ZoneId.of("UTC+8"));
 		instant = zonedDateTime.toInstant();
 	}
 
 	/**
-	 * 获取从公元1970年1月1日0时0分0 秒算起至今的UTC时间所经过的秒数。
+	 * 获取从公元1970年1月1日0时0分0 秒算起至今的UTC+8时间所经过的秒数。
 	 */
-	public long getSecondsStart1970UTC() {
+	public long getSecondsStart1970UTC8() {
 		return instant.getEpochSecond();
 	}
 
 	/**
-	 * 获取从公元1970年1月1日0时0分0 秒算起至今的UTC时间所经过的毫秒数。
+	 * 获取从公元1970年1月1日0时0分0 秒算起至今的UTC+8时间所经过的毫秒数。
 	 */
-	public long getMillSecondsStart1970UTC() {
+	public long getMillSecondsStart1970UTC8() {
 		return instant.toEpochMilli();
 	}
 }

+ 3 - 3
src/main/java/com/persagy/cameractl/utils/EnumTools.java

@@ -44,10 +44,10 @@ public class EnumTools {
 		}
 	};
 
-	/**------------------定义摄像头操作命令类型,海康SDK的命令对应的编码----------------------*/
+	/**------------------定义摄像头操作命令类型,派尔高SDK的命令对应的编码----------------------*/
 
-	/**---------------------------------定义摄像头操作命令类型,海康SDK的命令对应的编码-------------------------------------*/
-	public static int[] arrSdkCommandCode = new int[] { 5, 6, 2, 1, 3, 4, 7, 8, 9, 10, 11, 12, 13, 14 };
+	/**---------------------------------定义摄像头操作命令类型,派尔高SDK的命令对应的编码-------------------------------------*/
+	public static int[] arrSdkCommandCode = new int[] { 9, 10, 11, 12, 13, 14, 1, 2, 3, 4, 5, 7, 6, 8};
 
 	/**------------------定义摄像头操作命令类型,海康软件接口的命令----------------------*/
 	public static String[] arrHkvisionCommand = new String[] { "ZOOM_IN", "ZOOM_OUT", "FOCUS_NEAR", "FOCUS_FAR",

+ 15 - 14
src/main/java/com/persagy/cameractl/utils/OtherTools.java

@@ -1,15 +1,11 @@
 package com.persagy.cameractl.utils;
 
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-
-import com.persagy.cameractl.utils.EnumTools.OperatingSystem;
-
 import cn.hutool.core.util.StrUtil;
+import com.persagy.cameractl.utils.EnumTools.OperatingSystem;
 import lombok.extern.slf4j.Slf4j;
 
+import java.io.*;
+
 @Slf4j
 public class OtherTools {
 	// 播放mp4文件的url根地址
@@ -78,15 +74,20 @@ public class OtherTools {
 	};
 
 	public static String getVideoFileDir() {
-		String dllPath = "./config/tempVideo";
-		String newPath = "";
-		try {
-			File file1 = new File(dllPath);
-			newPath = file1.getCanonicalPath();
-		} catch (Exception e) {
+		String dllPath = "./tempVideo";
+		File file1 = new File(dllPath);
+
+		if (!file1.exists()) {
+			if (!file1.mkdirs()) {
+				throw new RuntimeException("create VideoFileDir fail");
+			}
 		}
 
-		return newPath;
+		try {
+			return file1.getCanonicalPath();
+		} catch (IOException e) {
+			throw new RuntimeException("VideoFileDir.getCanonicalPath fail", e);
+		}
 	}
 
 	public static String getVideoFilePath(Camera _camera) {

+ 6 - 4
src/main/java/com/persagy/cameractl/utils/TimerInterval.java

@@ -1,7 +1,9 @@
 package com.persagy.cameractl.utils;
 
 import java.io.File;
+import java.util.Objects;
 import java.util.Timer;
+import java.util.concurrent.TimeUnit;
 
 /**
  * 定时程序,目前包括:定时清除生成的mp4文件
@@ -27,14 +29,14 @@ class ClearTileTimer extends java.util.TimerTask {
 		if (!dir.exists() || !dir.isDirectory()) {// 判断是否存在目录
 			return;
 		}
-		// 五分钟
-		long timeP = 1000 * 60 * 5;
+		// 五分钟
+		long timeP = TimeUnit.MINUTES.toMillis(15);
 		long currTime = System.currentTimeMillis();
 		String[] files = dir.list();			// 读取目录下的所有目录文件信息
-		for (int i = 0; i < files.length; i++) {// 循环,添加文件名或回调自身
+		for (int i = 0; i < Objects.requireNonNull(files).length; i++) {// 循环,添加文件名或回调自身
 			File file = new File(dir, files[i]);
 			long lastModTime = file.lastModified();
-			// 5分钟以上的文件清除
+			// 超时的文件清除
 			if (currTime - lastModTime > timeP) {
 				file.delete();
 			}

+ 36 - 46
src/main/java/com/persagy/nvr/DataPlayCallBackClass.java

@@ -1,55 +1,45 @@
 package com.persagy.nvr;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
+import com.persagy.cameractl.common.VideoExportProcessContext;
 import com.sun.jna.Pointer;
-import com.sun.jna.platform.win32.WinDef.LPVOID;
-import com.sun.jna.platform.win32.WinDef.UINT;
+import com.sun.jna.platform.win32.WinDef;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.IOException;
 
+@Slf4j
 public class DataPlayCallBackClass implements VskClient.DataPlayCallBack {
-	Logger logger = LoggerFactory.getLogger(DataPlayCallBackClass.class);
-	// interface DataPlayCallBack extends Callback {
-	// void Callback(WinDef.UINT PlayHandle, WinDef.UINT DateType, byte[] pBuffer,
-	// WinDef.UINT BufferSize, WinDef.LPVOID pUser);
-	// }
-	@Override
-	public void Callback(UINT PlayHandle, int DateType, Pointer pBuffer, int BufferSize, LPVOID pUser) {
-		// byte[] byteArr = pBuffer.getByteArray(0, BufferSize);
-		// backByteBufferList.add(byteArr);
 
-		logger.error("数据回调,byteArr。length:" + BufferSize + ",DateType:" + DateType+",线程ID:"+Thread.currentThread().getId());
-		switch (DateType) {
-		case 0:
-			logger.error("********************************************系统头");
-			break;
-		case 2:
-			logger.error("********************************************最后");
-			break;
-		default:
-			break;
-		}
+    @Override
+    public void Callback(WinDef.UINT PlayHandle, WinDef.UINT DateType, Pointer pBuffer, WinDef.UINT BufferSize, WinDef.LPVOID pUser) {
+        // 这个回调输出的内容ffmpeg没法转码
+        // byte[] byteArr = pBuffer.getByteArray(0, BufferSize);
+        // backByteBufferList.add(byteArr);
+
+        // 0 系统头 1 混合码流数据  2 最后一包数据
+//		log.error("数据回调,byteArr。length:" + BufferSize + ",DateType:" + DateType+",线程ID:"+Thread.currentThread().getId());
+//		switch (DateType.intValue()) {
+//		case 0:
+//			logger.error("********************************************系统头");
+//			break;
+//		default:
+////			byte[] byteArr = pBuffer.getByteArray(0, BufferSize.intValue());
+////			AllStaticConfig.backByteBufferList.add(byteArr);
+//			break;
+//		}
 
-		if(pBuffer!=null) {
-			long size=Pointer.nativeValue(pBuffer);
-			pBuffer.clear(size);
-		}
-		if(pUser!=null) {
-			Pointer puser=pUser.getPointer();
-			long size2=Pointer.nativeValue(puser);
-			puser.clear(size2);
-		}
+        // try {
+        // RandomAccessFile randomFile = new
+        // RandomAccessFile("./config/tempVideo/a.mp4", "rw");
+        // long fileLength = randomFile.length();
+        // // 将写文件指针移到文件尾
+        // randomFile.seek(fileLength);
+        // randomFile.write(byteArr);
+        // randomFile.close();
+        // } catch (Exception e) {
+        // logger.error("byteArr写入异常:",e);
+        // }
+    }
 
-		// try {
-		// RandomAccessFile randomFile = new
-		// RandomAccessFile("./config/tempVideo/a.mp4", "rw");
-		// long fileLength = randomFile.length();
-		// // 将写文件指针移到文件尾
-		// randomFile.seek(fileLength);
-		// randomFile.write(byteArr);
-		// randomFile.close();
-		// } catch (Exception e) {
-		// logger.error("byteArr写入异常:",e);
-		// }
-	};
+    ;
 }

+ 39 - 69
src/main/java/com/persagy/nvr/EndPlayCallBackClass.java

@@ -1,86 +1,56 @@
 package com.persagy.nvr;
 
-import java.io.File;
-import java.io.FileOutputStream;
-
-import com.persagy.cameractl.conf.AllStaticConfig;
+import com.persagy.cameractl.common.VideoExportProcessContext;
 import com.persagy.cameractl.utils.OtherTools;
 import com.sun.jna.Pointer;
 import com.sun.jna.platform.win32.WinDef;
-
+import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
 
 @Slf4j
+@AllArgsConstructor
 public class EndPlayCallBackClass implements VskClient.PlayBackEndCallBack {
 
-	@Override
-	public void Callback(WinDef.UINT pbhandle, int errorcode, Pointer puser) {
-		log.info("回放结束");
-		if (errorcode == 0) {
-			// 根据SDK的PS流生成的MP4文件
-			String sourceToken = OtherTools.getMp4NamePrefix("_" + AllStaticConfig.playBackFileName);
-			// 播放结束了,生成源mp4文件
-			String sourceFilePath = OtherTools.getVideoFilePathByT(sourceToken);
-			try {
-				File sourceFile = new File(sourceFilePath);
-				sourceFile.createNewFile();// 有路径才能创建文件
+    private final VideoExportProcessContext context;
+
+    @Override
+    public void Callback(WinDef.UINT pbhandle, int errorcode, WinDef.LPVOID pUser) {
+        context.endTransmission();
+
+        boolean success = (errorcode == 0);
+        try {
+            if (!success || !context.hasData()) {
+                return;
+            }
+
+            String tempFilePath = context.getTempFilePath();
+            String filePath = context.getFilePath();
 
-				// 生成源MP4
-				byte[] dataByteArr = new byte[0];
-				int listSize = AllStaticConfig.backByteBufferList.size();
+            // 把源MP4转为页面上可播放的MP4
+            Runtime run = Runtime.getRuntime();
+            Process p = run.exec("ffmpeg -i \"" + tempFilePath + "\" -c copy -y \"" + filePath + "\"");
 
-				for (int i = 0; i < listSize; i++) {
-					byte[] currByteArr = AllStaticConfig.backByteBufferList.get(i);
-					byte[] tempArr = new byte[dataByteArr.length + currByteArr.length];
-					// 参数依次为:源数组、从其开始复制的源数组索引、目标数组、开始放入数据的目标数组索引、要复制的元素数
-					System.arraycopy(dataByteArr, 0, tempArr, 0, dataByteArr.length);
-					System.arraycopy(currByteArr, 0, tempArr, dataByteArr.length, currByteArr.length);
-					dataByteArr = tempArr;
-				}
+            // 读取标准输入流、输出流,防止进程阻塞
+            // 标准输入流(必须写在 waitFor 之前)
+            String inputStreamStr = OtherTools.consumeInputStream(p.getInputStream());
+            // 标准错误流(必须写在 waitFor 之前)
+            String errStreamStr = OtherTools.consumeInputStream(p.getErrorStream());
 
-				FileOutputStream fos = new FileOutputStream(sourceFile);
-				fos.write(dataByteArr);
-				fos.close();
+            int retCode = p.waitFor();
 
-				// 把源MP4转为页面上可播放的MP4
-				Runtime run = Runtime.getRuntime();
-				Process p = run.exec(
-						"ffmpeg -i \"" + sourceFilePath + "\" -c copy -y \"" + AllStaticConfig.playBackFilePath + "\"");
-				// 释放进程
-				// p.getOutputStream().close();
-				// p.getInputStream().close();
-				// p.getErrorStream().close();
+            if (retCode != 0) {
+                throw new RuntimeException("ffmpeg转换mp4时失败,错误信息:" +
+                        StringUtils.defaultIfEmpty(errStreamStr, inputStreamStr));
+            }
 
-				// 读取标准输入流、输出流,防止进程阻塞
-				// 标准输入流(必须写在 waitFor 之前)
-				String inputStreamStr = OtherTools.consumeInputStream(p.getInputStream());
-				// 标准错误流(必须写在 waitFor 之前)
-				String errStreamStr = OtherTools.consumeInputStream(p.getErrorStream());
+            p.destroy();
+        } catch (Exception e) {
+            log.error("生成回放mp4时异常:", e);
+            success = false;
+        } finally {
+            context.complete(success);
+        }
+    }
 
-				int retCode = p.waitFor();
-				if (retCode == 0) {
-					AllStaticConfig.backByteBufferList.clear();
-					// 正常转换结束
-					AllStaticConfig.playBackMp4State = 1;
-				} else {
-					// 转换出错
-					String errStr = errStreamStr != null ? errStreamStr : inputStreamStr;
-					log.error("ffmpeg转换mp4时失败,错误信息:" + errStr);
-					AllStaticConfig.backByteBufferList.clear();
-					AllStaticConfig.playBackMp4State = 2;
-				}
-				p.destroy();
-				sourceFile.delete();
-			} catch (Exception e) {
-				log.error("生成回放mp4时异常:", e);
-				AllStaticConfig.backByteBufferList.clear();
-				AllStaticConfig.playBackMp4State = 2;
-			}
-		} else {
-			log.error("回放结束时错误,错误码::" + errorcode);
-			AllStaticConfig.backByteBufferList.clear();
-			AllStaticConfig.playBackMp4State = 2;
-		}
-	};
-	
 }

+ 2 - 1
src/main/java/com/persagy/nvr/GlobalExceptionHandler.java

@@ -2,6 +2,7 @@ package com.persagy.nvr;
 
 import java.io.IOException;
 
+import com.persagy.cameractl.utils.ResultTools;
 import org.springframework.beans.ConversionNotSupportedException;
 import org.springframework.http.converter.HttpMessageNotReadableException;
 import org.springframework.http.converter.HttpMessageNotWritableException;
@@ -133,7 +134,7 @@ public class GlobalExceptionHandler {
 
     private <T extends Throwable> String resultFormat(Integer code, T ex) {
         log.error(String.format(logExceptionFormat, code, ex.getMessage()));
-        return code+":"+ ex.getMessage();
+        return ResultTools.errorResult(code+":"+ ex.getMessage());
     }
 
 	    

+ 25 - 25
src/main/java/com/persagy/nvr/VideoNoViskHeadFrameCallBackClass.java

@@ -1,38 +1,38 @@
 package com.persagy.nvr;
 
-import com.persagy.cameractl.conf.AllStaticConfig;
+import com.persagy.cameractl.common.VideoExportProcessContext;
 import com.persagy.nvr.VskClient.VideoNoViskHeadFrameInfo;
 import com.sun.jna.Pointer;
 import com.sun.jna.Structure;
-
 import lombok.extern.slf4j.Slf4j;
 
 @Slf4j
 public class VideoNoViskHeadFrameCallBackClass implements VskClient.VideoNoViskHeadFrameCallBack {
 
-	@Override
-	public void Callback(Pointer pFrameBuf, int nFrameLen, Pointer pStream, Pointer pUser) {
-		try {
-			if (pFrameBuf == null || pStream == null) {
-				log.info("自定义回调没数据");
-				return;
-			}
-
-			VideoNoViskHeadFrameInfo videoNoViskHeadFrameInfo = Structure.newInstance(VideoNoViskHeadFrameInfo.class,
-					pStream);
-			videoNoViskHeadFrameInfo.read();
-
-			int encodeType = videoNoViskHeadFrameInfo.nEncodeType.intValue();
-			if (encodeType != 26) {
-				log.warn("非h264编码,当前编码类型枚举值为:" + encodeType);
-			}
-
-			byte[] byteArr = pFrameBuf.getByteArray(0, nFrameLen);
-			AllStaticConfig.backByteBufferList.add(byteArr);
-		} catch (Exception e) {
-			log.error("自定义回调执行异常", e);
-		}
-	}
+    @Override
+    public void Callback(Pointer pFrameBuf, int nFrameLen, Pointer pStream, Pointer pUser) {
+        try {
+            if (pFrameBuf == null || pStream == null) {
+                log.info("自定义回调没数据");
+                return;
+            }
+
+            VideoNoViskHeadFrameInfo videoNoViskHeadFrameInfo = Structure.newInstance(
+                    VideoNoViskHeadFrameInfo.class,
+                    pStream);
+
+            videoNoViskHeadFrameInfo.read();
+
+            int encodeType = videoNoViskHeadFrameInfo.nEncodeType.intValue();
+            if (encodeType != 26) {
+                log.warn("非h264编码,当前编码类型枚举值为:" + encodeType);
+            }
+
+            VideoExportProcessContext.getCurrentContext().write(pFrameBuf.getByteArray(0, nFrameLen));
+        } catch (Exception e) {
+            log.error("自定义回调执行异常", e);
+        }
+    }
 }
 
 // 编码类型

+ 9 - 6
src/main/java/com/persagy/nvr/VskClient.java

@@ -18,6 +18,9 @@ import com.sun.jna.platform.win32.WinDef.ULONGLONGByReference;
 import com.sun.jna.ptr.IntByReference;
 import com.sun.jna.win32.StdCallLibrary.StdCallCallback;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * VX-1000-SDK无服务器API接口
  *
@@ -391,7 +394,7 @@ public interface VskClient extends Library {
 	// PlayBackEndCallBack fun, WinDef.DWORDLONG nUserData, WinDef.LPVOID pUser,
 	// WinDef.DWORDLONG pTotalSize, WinDef.DWORDByReference pnPlaybackID);
 	int JsonSdk_PlayBackStartByTime(DWORD UserID, String pInfo, DataPlayCallBack VideoDataCallBack,
-			PlayBackEndCallBack fun, ULONGLONG nUserData, LPVOID pUser, ULONGLONGByReference pTotalSize,
+			PlayBackEndCallBack fun, ULONGLONG nUserData, LPVOID pUser, WinDef.DWORDLONG pTotalSize,
 			DWORDByReference pnPlaybackID);
 
 	/**
@@ -488,9 +491,9 @@ public interface VskClient extends Library {
 	 *            下载句柄ID, 0 表示失败,其他值作为JsonSdk_DownloadStop的句柄参数。
 	 * @return 成功返回0,失败返回错误码
 	 */
-	int JsonSdk_DownloadStartByTime(WinDef.DWORD userID, byte[] pInfo, DataPlayCallBack DownloadDataCallBack,
-			PlayBackEndCallBack fun, byte[] pSavedFileName, WinDef.LPVOID pUser, int FileType,
-			WinDef.DWORDLONG pTotalSize, WinDef.DWORDByReference pnDownID);
+	int JsonSdk_DownloadStartByTime(WinDef.DWORD userID, String pInfo, DataPlayCallBack DownloadDataCallBack,
+			PlayBackEndCallBack fun, String pSavedFileName, WinDef.LPVOID pUser, int FileType,
+									WinDef.DWORDByReference pTotalSize, WinDef.DWORDByReference pnDownID);
 
 	/**
 	 * 录像下载的控制
@@ -3225,7 +3228,7 @@ public interface VskClient extends Library {
 	interface DataPlayCallBack extends StdCallCallback {
 		// void Callback(WinDef.UINT PlayHandle, WinDef.UINT DateType, Byte[] pBuffer,
 		// WinDef.UINT BufferSize, WinDef.LPVOID pUser);
-		void Callback(UINT PlayHandle, int DateType, Pointer pBuffer, int BufferSize, LPVOID pUser);
+		void Callback(WinDef.UINT PlayHandle, WinDef.UINT DateType, Pointer pBuffer, WinDef.UINT BufferSize, WinDef.LPVOID pUser);
 		// public void invoke(UINT lPlayHandle, int dwDataType,ByteByReference
 		// pBuffer,UINT int dwBufSize, int dwUser);
 	}
@@ -3237,7 +3240,7 @@ public interface VskClient extends Library {
 	 */
 	interface PlayBackEndCallBack extends StdCallCallback {
 		// void Callback(WinDef.UINT pbhandle, int errorcode, Pointer puser);
-		void Callback(UINT pbhandle, int errorcode, Pointer puser);
+		void Callback(UINT pbhandle, int errorcode, WinDef.LPVOID pUser);
 	}
 
 	/**