瀏覽代碼

Merge remote-tracking branch 'origin/likeadmin' into likeadmin

# Conflicts:
#	taphole-camera/src/main/java/com.sckj.camera/controller/CameraController.java
zhanghao 5 月之前
父節點
當前提交
c5a4c4d2be

+ 22 - 75
docker/docker-compose.yml

@@ -10,26 +10,6 @@ networks:
 #定义了要运行的服务列表
 services:
 
-  #  maven:
-  #    container_name: taphole-java-maven
-  #    image: maven:3.8.6-openjdk-8
-  #   # restart: always
-  #    tty: true
-  #    working_dir: /taphole_java/server
-  #    volumes:
-  #      - ./server:/taphole_java/server
-  #    networks:
-  #      - taphole
-  #    ports:
-  #      - "58080:28080"
-  #    command: bash
-  #    depends_on:
-  #      - mysql
-  #      - redis
-  #      - zlmediakit
-
-
-
   mysql:
     container_name: taphole-mysql
     image: mysql:5.7.29 #X86架构
@@ -74,62 +54,29 @@ services:
       - "56379:6379"
     command: redis-server --appendonly yes
 
-  #  node:
-  #    container_name: taphole-node
-  #    image: node:14.18.1
-  #   # restart: always
-  #    volumes:
-  #      - ./node/admin:/taphole_node/admin
-  #    networks:
-  #      - taphole
-  #    tty: true
-  #    working_dir: /taphole_node/admin
-  #    ports:
-  #      - "55173:5173"
 
-  zlmediakit:
-    image: zlmediakit:latest
-    container_name: taphole-zlmediakit
-    ports:
-      - "1935:1935"  # RTMP 端口
-      - "8000:8000"  # HTTP 端口
-      - "8554:8554"  # RTSP 端口
-      - "8083:8083"  # WebRTC 端口
-      - "18000:80"    # HTTP 端口
-    volumes:
-      - ./zlmediakit/config:/opt/zlmediakit/config  # 挂载配置文件目录
-      - ./zlmediakit/media:/opt/zlmediakit/media    # 挂载媒体文件目录
-    networks:
-      taphole:
-        ipv4_address: 172.28.1.10
+#  zlmediakit:
+#    image: zlmediakit:latest
+#    container_name: taphole-zlmediakit
+#    ports:
+#      - "1935:1935"  # RTMP 端口
+#      - "8000:8000"  # HTTP 端口
+#      - "8554:8554"  # RTSP 端口
+#      - "8083:8083"  # WebRTC 端口
+#      - "18000:80"    # HTTP 端口
+#    volumes:
+#      - ./zlmediakit/config:/opt/zlmediakit/config  # 挂载配置文件目录
+#      - ./zlmediakit/media:/opt/zlmediakit/media    # 挂载媒体文件目录
+#    networks:
+#      taphole:
+#        ipv4_address: 172.28.1.10
 
-  #  zlmediakit:
-  #    container_name: taphole-zlmediakit
-  #    image: zlmediakit:latest
-  #  #  restart: always
-  #    volumes:
-  #      - ./zlmediakit/logs:/opt/media/bin/log
-  #      - ./zlmediakit/data/www:/opt/media/bin/www
-  #      - ./zlmediakit/conf/config.ini:/opt/media/conf/config.ini
-  #      - ./zlmediakit/conf/default.pem:/opt/media/bin/default.pem
-  #    expose:
-  #      - "80"
-  #      - "443"
-  #      - "554"
-  #      - "1935"
-  #    ports:
-  #      - "51935:1935"
-  #      - "58082:80"
-  #      - "58443:443"
-  #      - "58554:554"
-  #      - "50000:10000"
-  #      - "50000:10000/udp"
-  #      - "5000:8000/udp"
-  #      - "59000:9000/udp"
-  #      - "50000-50500:30000-30500"
-  #      - "50000-50500:30000-30500/udp"
-  #    networks:
-  #      - taphole
+  webrtc:
+    image: webrtc-streamer:latest
+    container_name: taphole-webrtc
+    stdin_open: true
+    tty: true
+    network_mode: "host"
 
 
   nginx:
@@ -171,7 +118,7 @@ services:
     depends_on:
       - mysql
       - redis
-      - zlmediakit
+      - webrtc
     networks:
       taphole:
         ipv4_address: 172.28.1.100

+ 1 - 0
taphole-admin/src/main/resources/application-dev.yml

@@ -39,6 +39,7 @@ camera:
   rtmp:
     rtmphost: 127.0.0.1:1935
     httphost: 127.0.0.1:80
+    webrtchost: 192.168.110.130:8000
 
 socketio:
  # host: 127.0.0.1		#主机名,默认是 0.0.0.0 (这个设不设置无所谓,因为后面的 SocketConfig 类一般不用设置这个)

+ 1 - 0
taphole-admin/src/main/resources/application-prod.yml

@@ -32,6 +32,7 @@ camera:
   rtmp:
     rtmphost: 192.168.110.130:1935
     httphost: 192.168.110.130:18000
+    webrtchost: 192.168.110.130:8000
 
 socketio:
   #host: 0.0.0.0		#主机名,默认是 0.0.0.0 (这个设不设置无所谓,因为后面的 SocketConfig 类一般不用设置这个)

+ 9 - 13
taphole-admin/src/main/resources/application-test.yml

@@ -4,25 +4,24 @@ like:
 
 # 服务配置
 server:
-  port: 28080
+  port: 8080
   servlet:
     context-path: /
-
 # 框架配置
 spring:
   # 数据源配置
   datasource:
-    url: jdbc:mysql://192.168.110.130:13306/taphole?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false
+    url: jdbc:mysql://mysql:3306/taphole?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false
     type: com.alibaba.druid.pool.DruidDataSource # 数据源类型
     driver-class-name: com.mysql.jdbc.Driver # MySql的驱动
     username: root # 数据库账号
     password: root # 数据库密码
   # Redis配置
   redis:
-    host: 101.37.148.192   # Redis服务地址
+    host: redis   # Redis服务地址
     port: 6379        # Redis端口
-    password: sckj@1234        # Redis密码
-    database: 5       # 数据库索引
+#    password: sckj@1234        # Redis密码
+#    database: 5       # 数据库索引
 
 # Mybatis-plus配置 【是否开启SQL日志输出】
 #mybatis-plus:
@@ -31,15 +30,12 @@ spring:
 camera:
   filepath: /home/xiaofei/uploads/taphole/
   rtmp:
-    rtmphost: 192.168.110.130:1935
-    httphost: 192.168.110.130:80
-
-#rtmp:
-#    rtmphost: 127.0.0.1:1935
-#    httphost: 127.0.0.1:80
+    rtmphost: 172.28.1.10:1935
+    httphost: 150.158.22.179:18000
+    webrtchost: 192.168.110.130:8000
 
 socketio:
-#  host: 192.168.110.130	#主机名,默认是 0.0.0.0 (这个设不设置无所谓,因为后面的 SocketConfig 类一般不用设置这个)
+  #host: 0.0.0.0		#主机名,默认是 0.0.0.0 (这个设不设置无所谓,因为后面的 SocketConfig 类一般不用设置这个)
   port: 33000			#监听端口
   maxFramePayloadLength: 1048576
   maxHttpContentLength: 1048576

+ 1 - 1
taphole-admin/src/main/resources/application.yml

@@ -4,7 +4,7 @@ like:
   # 验证码配置
   captcha:
     # 是否开启验证码
-    status: true
+    status: false
     # 验证码有效时长
     expire: 120
     # 验证码缓存键名

+ 6 - 6
taphole-camera/src/main/java/com.sckj.camera/aop/HikCameraAspect.java

@@ -130,12 +130,12 @@ public class HikCameraAspect {
      * @return
      */
     private int login(CameraDTO cameraDTO, CameraLogin.Operation type) {
-        if (CameraLogin.Operation.REAL_STREAM == type || CameraLogin.Operation.BACK_STREAM == type) {
-            //推流需要检查多媒体服务器是否启动
-            if (!FlowUtils.isRTMPServerRunning(cameraProperties.getRtmp().getRtmphost())) {
-                throw new OperateException("RTMP服务器未启动,推流失败");
-            }
-        }
+//        if (CameraLogin.Operation.REAL_STREAM == type || CameraLogin.Operation.BACK_STREAM == type) {
+//            //推流需要检查多媒体服务器是否启动
+//            if (!FlowUtils.isRTMPServerRunning(cameraProperties.getRtmp().getRtmphost())) {
+//                throw new OperateException("RTMP服务器未启动,推流失败");
+//            }
+//        }
         cameraDTO.setHcTool(new HCNetTools());
         HCNetTools hcTool = cameraDTO.getHcTool();
 

+ 3 - 1
taphole-camera/src/main/java/com.sckj.camera/controller/CameraFileController.java

@@ -8,6 +8,7 @@ import io.swagger.annotations.ApiImplicitParams;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
+import springfox.documentation.annotations.ApiIgnore;
 
 import javax.servlet.http.HttpServletResponse;
 
@@ -15,9 +16,10 @@ import javax.servlet.http.HttpServletResponse;
  * Description:
  * Author:sckj
  */
-@Api(tags = "文件上传下载")
+@Api(tags = "相机文件下载")
 @RestController
 @RequestMapping(value = "/camerafile")
+@ApiIgnore
 public class CameraFileController {
 
     @Autowired

+ 3 - 1
taphole-camera/src/main/java/com.sckj.camera/controller/CameraFlowController.java

@@ -11,10 +11,12 @@ import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
+import springfox.documentation.annotations.ApiIgnore;
 
-@Api(tags = "视频流")
+@Api(tags = "相机视频流")
 @RestController
 @RequestMapping(value = "/flow")
+@ApiIgnore
 public class CameraFlowController {
 
     @Autowired

+ 94 - 1
taphole-camera/src/main/java/com.sckj.camera/hik/HCNetTools.java

@@ -1,10 +1,17 @@
 package com.sckj.camera.hik;
 
 import com.sun.jna.Pointer;
+import com.sun.jna.ptr.ByteByReference;
 import com.sun.jna.ptr.IntByReference;
+import org.apache.commons.lang3.ObjectUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.time.Instant;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
@@ -365,7 +372,7 @@ public class HCNetTools {
 		HCNetSDK.NET_DVR_WORKSTATE_V30 devwork = new HCNetSDK.NET_DVR_WORKSTATE_V30();
 		if (!hCNetSDK.NET_DVR_GetDVRWorkState_V30(lUserID, devwork)) {
 			// 返回Boolean值,判断是否获取设备能力
-			logger.info("hksdk(抓图)-返回设备状态失败");
+			logger.info("返回设备状态失败");
 			return -1;
 		}
 		return devwork.dwDeviceStatic;
@@ -400,4 +407,90 @@ public class HCNetTools {
 		}
 	}
 
+	public  static class FRealDataCallBack_V30_Impl implements HCNetSDK.FRealDataCallBack_V30{
+		private String fileName;
+		private OutputStream ffmpegInput;
+
+		public FRealDataCallBack_V30_Impl(String fileName){
+			this.fileName = fileName;
+		}
+
+		public FRealDataCallBack_V30_Impl(OutputStream ffmpegInput){
+			this.ffmpegInput = ffmpegInput;
+		}
+
+		@Override
+		public void invoke(int lRealHandle, int dwDataType, ByteByReference pBuffer, int dwBufSize, Pointer pUser) {
+			if (dwDataType == HCNetSDK.NET_DVR_SYSHEAD) {
+				// 处理系统头信息
+			} else if (dwDataType == HCNetSDK.NET_DVR_STREAMDATA) {
+				// 处理视频流数据
+				try {
+					if(dwBufSize>0){
+						if(ObjectUtils.isNotEmpty(fileName)){
+							FileOutputStream fos = new FileOutputStream(new File(fileName));
+							fos.write(pBuffer.getPointer().getByteArray(0,dwBufSize), 0, dwBufSize);
+							fos.close();
+						}else if(ObjectUtils.isNotEmpty(ffmpegInput)){
+							// 获取当前系统时间
+							Instant now = Instant.now();
+
+							// 假设时间戳在数据包的前8个字节中
+							long packetTimestamp = 0;
+							for (int i = 0; i < 8 && i < dwBufSize; i++) {
+								packetTimestamp |= ((long) (pBuffer.getPointer().getByteArray(0,dwBufSize)[i] & 0xFF)) << (56 - i * 8);
+							}
+
+							// 计算延迟
+							long delayMs = (now.toEpochMilli() - packetTimestamp) / 1000;
+
+							// 输出延迟信息
+							System.out.println("Packet Timestamp: " + packetTimestamp + ", Current Time: " + now.toEpochMilli() + ", Delay: " + delayMs + " ms");
+
+							ffmpegInput.write(pBuffer.getPointer().getByteArray(0,dwBufSize), 0, dwBufSize);
+						}
+					}
+				} catch (IOException e) {
+					e.printStackTrace();
+				}
+			}
+		}
+	}
+
+	public int getRealStreamData(HCNetSDK.FRealDataCallBack_V30 fRealDataCallBack){
+		// 预览参数
+		HCNetSDK.NET_DVR_PREVIEWINFO previewInfo = new HCNetSDK.NET_DVR_PREVIEWINFO();
+		previewInfo.lChannel = 1; // 通道号
+		previewInfo.dwStreamType = 0; // 0-主码流,1-子码流,2-三码流,3-虚拟码流,以此类推
+		previewInfo.bBlocked = 0; // 0- 非阻塞取流,1- 阻塞取流
+		previewInfo.byProtoType = 0; //应用层取流协议:0- 私有协议,1- RTSP协议
+		previewInfo.dwLinkMode = 1; //连接方式:0- TCP方式,1- UDP方式,2- 多播方式,3- RTP方式,4- RTP/RTSP,5- RTP/HTTP,6- HRUDP(可靠传输) ,7- RTSP/HTTPS,8- NPQ
+		return  hCNetSDK.NET_DVR_RealPlay_V40(lUserID, previewInfo, fRealDataCallBack, null);
+	}
+
+
+//	public int getESRealStreamData(HCNetSDK.FRealDataCallBack_V30 fRealDataCallBack){
+//		// 预览参数
+//		HCNetSDK.NET_DVR_PREVIEWINFO previewInfo = new HCNetSDK.NET_DVR_PREVIEWINFO();
+//		previewInfo.lChannel = 1; // 通道号
+//		previewInfo.dwStreamType = 0; // 0-主码流,1-子码流,2-三码流,3-虚拟码流,以此类推
+//		previewInfo.bBlocked = 1; // 0- 非阻塞取流,1- 阻塞取流
+//		previewInfo.byProtoType = 0; //应用层取流协议:0- 私有协议,1- RTSP协议
+//		previewInfo.dwLinkMode = 0; //连接方式:0- TCP方式,1- UDP方式,2- 多播方式,3- RTP方式,4- RTP/RTSP,5- RTP/HTTP,6- HRUDP(可靠传输) ,7- RTSP/HTTPS,8- NPQ
+//		int handle =   hCNetSDK.NET_DVR_RealPlay_V40(lUserID, previewInfo, fRealDataCallBack, null);
+//		if (handle == -1) {
+//			int iErr = hCNetSDK.NET_DVR_GetLastError();
+//			System.err.println("取流失败" + iErr);
+//			return -1;
+//		}
+//
+//		if (!hCNetSDK.NET_DVR_SetES(Handle, fPlayescallback, null)) {
+//			System.err.println("设置裸码流回调失败,错误码:" + hCNetSDK.NET_DVR_GetLastError());
+//		}
+//
+//	}
+
+
+
+
 }

+ 10 - 0
taphole-camera/src/main/java/com.sckj.camera/manager/CameraProperties.java

@@ -21,6 +21,8 @@ public class CameraProperties {
 
         private String httphost;
 
+        private String webrtchost;
+
         public String getRtmphost() {
             return rtmphost;
         }
@@ -36,6 +38,14 @@ public class CameraProperties {
         public void setHttphost(String httphost) {
             this.httphost = httphost;
         }
+
+        public String getWebrtchost() {
+            return webrtchost;
+        }
+
+        public void setWebrtchost(String webrtchost) {
+            this.webrtchost = webrtchost;
+        }
     }
 
     public String getFilepath() {

+ 2 - 0
taphole-camera/src/main/java/com.sckj.camera/model/dto/ResultDTO.java

@@ -12,6 +12,8 @@ public class ResultDTO {
     private String rtmpUrl;
     private String flvUrl;
     private String hlsUrl;
+    private String rtspUrl;
+    private String webrtcUrl;
 
     public ResultDTO() {}
 

+ 62 - 12
taphole-camera/src/main/java/com.sckj.camera/service/CameraServiceImpl.java

@@ -160,7 +160,8 @@ public class CameraServiceImpl extends ServiceImpl<CameraMapper, Camera> {
      * @param rtspName
      * @return
      */
-    private ResultDTO pushStream(CameraDTO cameraDTO, String appName, String rtspName) {
+    private ResultDTO pushStream(CameraDTO cameraDTO, String appName, String rtspName)   {
+        ResultDTO resultDTO = new ResultDTO();
         String rtmpUrl = "rtmp://" + cameraProperties.getRtmp().getRtmphost() + "/live/" + appName; //rtmp链
         String flvUrl = "http://" + cameraProperties.getRtmp().getHttphost() + "/live/" + appName + ".live.flv"; //live-flv链
         String hlsUrl = "http://" + cameraProperties.getRtmp().getHttphost() + "/live/" + appName + "/hls.m3u8"; //hls链
@@ -173,9 +174,63 @@ public class CameraServiceImpl extends ServiceImpl<CameraMapper, Camera> {
             flowService.removeById(cameraFlow.getId());
         }
 
+//        // 初始化Socket.IO服务器
+//        Configuration config = new Configuration();
+//        config.setHostname("0.0.0.0");
+//        config.setPort(3000);
+//        server = new SocketIOServer(config);
+//        server.addListeners(new DataListener<byte[]>() {
+//            @Override
+//            public void onData(SocketIOClient client, byte[] data, AckRequest ackSender) throws Exception {
+//                // 处理接收到的数据
+//            }
+//
+//        });
+//
+//        server.addEventListener("videoFrame", byte[].class, (client, data, ackRequest) -> {
+//            // 发送视频帧数据
+//            client.sendEvent("videoFrame", data);
+//        });
+//
+//        server.start();
+//        System.out.println("Socket.IO server started on port 3000");
+//
+//        HCNetTools hcTool = cameraDTO.getHcTool();
+//        hcTool.getRealStreamData(new HCNetSDK.FRealDataCallBack_V30() {
+//            @Override
+//            public void invoke(int lRealHandle, int dwDataType, ByteByReference pBuffer, int dwBufSize, Pointer pUser) {
+//                try {
+//                    if (dwDataType == HCNetSDK.NET_DVR_STREAMDATA) {
+//                        if(dwBufSize > 0){
+//                            // 获取所有连接的客户端
+//                            if(ObjectUtils.isEmpty(server.getAllClients())){
+//                                return;
+//                            }
+//
+//                            System.out.println("client size:" + server.getAllClients().size());
+//                            for (SocketIOClient client : server.getAllClients()) {
+//                                System.out.println("client...");
+//                                client.sendEvent("videoFrame",pBuffer.getPointer().getByteArray(0,dwBufSize),0,dwBufSize);
+//                            }
+//                        }
+//                    }
+//                } catch (Exception e) {
+//                    e.printStackTrace();
+//                }
+//            }
+//        });
+//
+//        try {
+//            Thread.sleep(Long.MAX_VALUE);
+//        } catch (InterruptedException e) {
+//            e.printStackTrace();
+//        }
+
+
+
         //开始推流
         String taskName = ffmpegService.startTranscoding(appName, rtspName, rtmpUrl);
-        ResultDTO resultDTO = new ResultDTO();
+
         if (ObjectUtils.isNotEmpty(taskName)) {
             resultDTO.setTaskName(taskName);
             resultDTO.setResult("success");
@@ -214,10 +269,10 @@ public class CameraServiceImpl extends ServiceImpl<CameraMapper, Camera> {
             resultDTO.setResult("未找到该设备");
             return resultDTO;
         }
-        String appName = cameraDTO.getAccount() + cameraDTO.getIp().replace(".", "");
         String channelNumberStr = cameraDTO.getChannelNumber() + "02"; //新通道(2012年之后设备,02代表子码流)
         String rtspName = "rtsp://" + cameraDTO.getAccount() + ":" + cameraDTO.getPassword() + "@" + cameraDTO.getIp() + ":554/" + channelNumberStr + "?transportmode=unicast"; //新码流
-        resultDTO = pushStream(cameraDTO, appName, rtspName);
+        resultDTO.setRtspUrl(rtspName);
+        resultDTO.setWebrtcUrl("http://"+cameraProperties.getRtmp().getWebrtchost());
         return resultDTO;
     }
 
@@ -241,19 +296,13 @@ public class CameraServiceImpl extends ServiceImpl<CameraMapper, Camera> {
             startTime = sdf.format(cameraDTO.getHistoryDTO().getStartTime());
             endTime = null;
         }
-        String appName;
         if (cameraDTO.getType() == 1) {
             //摄像机
-            appName = "history" + cameraDTO.getVcr().getAccount() + cameraDTO.getVcr().getIp().replace(".", "") + "to" + cameraDTO.getIp().replace(".", "") + "start" + startTime + "end" + endTime;
+            //appName = "history" + cameraDTO.getVcr().getAccount() + cameraDTO.getVcr().getIp().replace(".", "") + "to" + cameraDTO.getIp().replace(".", "") + "start" + startTime + "end" + endTime;
         } else {
             throw new OperateException("目前只支持带有录像机的回放数据");
             //NVR
-            //appName = "history" + cameraDTO.getAccount() + cameraDTO.getIp().replace(".", "") + "to" + cameraDTO.getIpcAddress().replace(".", "") + "start" + startTime + "end" + endTime;
-            //channelNumber = HikCameraUtils.getIpcChannel(cameraDTO.getChannelList(), cameraDTO.getIpcAddress());
         }
-//        if (channelNumber == -1) {
-//            logger.info("历史流-未搜索到ip:" + cameraDTO.getIp() + ", ipc:" + cameraDTO.getIpcAddress() + " 的设备");
-//        }
         //未推流
         String channelNumberStr = cameraDTO.getChannelNumber() + "01"; //回放流只有主码流
         String rtspName = "rtsp://";
@@ -262,7 +311,8 @@ public class CameraServiceImpl extends ServiceImpl<CameraMapper, Camera> {
         } else {
             rtspName += cameraDTO.getVcr().getAccount() + ":" + cameraDTO.getVcr().getPassword() + "@" + cameraDTO.getVcr().getIp() + ":554/Streaming/tracks/" + channelNumberStr + "?starttime=" + startTime;
         }
-        resultDTO = pushStream(cameraDTO, appName, rtspName);
+        //resultDTO = pushStream(cameraDTO, appName, rtspName);
+        resultDTO.setRtspUrl(rtspName);
         return resultDTO;
     }