diff --git a/rtsp-vue/vite.config.js b/rtsp-vue/vite.config.js index 2b6f847..0b0ba8a 100644 --- a/rtsp-vue/vite.config.js +++ b/rtsp-vue/vite.config.js @@ -25,7 +25,7 @@ export default defineConfig(({ mode, command }) => { }, // vite 相关配置 server: { - port: 80, + port: 1024, host: true, open: true, proxy: { diff --git a/ruoyi-admin/src/main/resources/application-druid.yml b/ruoyi-admin/src/main/resources/application-druid.yml index 75a5816..241b09e 100644 --- a/ruoyi-admin/src/main/resources/application-druid.yml +++ b/ruoyi-admin/src/main/resources/application-druid.yml @@ -6,9 +6,9 @@ spring: druid: # 主库数据源 master: - url: jdbc:mysql://localhost:3306/ry_face?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 - username: root - password: 'sVYLnUv0QOWDTyVA' + url: jdbc:mysql://140.143.206.120:3306/fad_watch?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + username: klp + password: KeLunPu123@ # 从库数据源 slave: # 从数据源开关/默认关闭 diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 23136dc..e0a5952 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -7,7 +7,7 @@ ruoyi: # 版权年份 copyrightYear: 2025 # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath) - profile: D:/ruoyi/uploadPath + profile: /home/wangyu/uploadPath # 获取ip地址开关 addressEnabled: false # 验证码类型 math 数字计算 char 字符验证 @@ -64,7 +64,7 @@ spring: devtools: restart: # 热部署开关 - enabled: true + enabled: false data: # redis 配置 redis: diff --git a/ruoyi-admin/src/main/resources/logback.xml b/ruoyi-admin/src/main/resources/logback.xml index a360583..800431e 100644 --- a/ruoyi-admin/src/main/resources/logback.xml +++ b/ruoyi-admin/src/main/resources/logback.xml @@ -1,7 +1,7 @@ - + diff --git a/ruoyi-video/src/main/java/com/ruoyi/video/init/InitServer.java b/ruoyi-video/src/main/java/com/ruoyi/video/init/InitServer.java index 56873a2..2b0f027 100644 --- a/ruoyi-video/src/main/java/com/ruoyi/video/init/InitServer.java +++ b/ruoyi-video/src/main/java/com/ruoyi/video/init/InitServer.java @@ -59,8 +59,8 @@ public class InitServer implements CommandLineRunner { public void run(String... args) throws Exception { // 初始化自动播放 this.initAutoPlay(); - // 获取本机IP地址 - String ip = InetAddress.getLocalHost().getHostAddress(); + // TODO 获取本机IP地址 + String ip = "localhost"; // 获取http端口号 String httpPort = this.env.getProperty("server.port"); // 获取web路径 @@ -80,7 +80,7 @@ public class InitServer implements CommandLineRunner { "\t wsflv: \t ws://{}:{}/live?url={您的源地址} \n" + "\t hls(m3u8): \t http://{}:{}/hls?url={您的源地址} \n" + "\t h2-database: \t http://{}:{}/h2-console \n" + - "\t 作者微信: \t chenbai0511 \n" + + "\t 作者微信: \t yu5132310 \n" + "\t 请我喝杯咖啡吧qwq \n" + "--------------------------------------------------------- \n", port, diff --git a/ruoyi-video/src/main/java/com/ruoyi/video/server/FlvHandler.java b/ruoyi-video/src/main/java/com/ruoyi/video/server/FlvHandler.java index b62a65a..ab5c306 100644 --- a/ruoyi-video/src/main/java/com/ruoyi/video/server/FlvHandler.java +++ b/ruoyi-video/src/main/java/com/ruoyi/video/server/FlvHandler.java @@ -91,7 +91,7 @@ public class FlvHandler extends SimpleChannelInboundHandler { } CameraDto cameraDto = buildCamera(req.uri()); - + System.out.println(cameraDto); if (StrUtil.isBlank(cameraDto.getUrl())) { log.info("url有误"); sendError(ctx, HttpResponseStatus.BAD_REQUEST); @@ -100,6 +100,7 @@ public class FlvHandler extends SimpleChannelInboundHandler { if (!req.decoderResult().isSuccess() || (!"websocket".equals(req.headers().get("Upgrade")))) { // http请求 + System.out.println("q3gegyaisgd"); sendFlvReqHeader(ctx); mediaService.playForHttp(cameraDto, ctx); diff --git a/ruoyi-video/src/main/java/com/ruoyi/video/thread/MediaTransferFlvByJavacv.java b/ruoyi-video/src/main/java/com/ruoyi/video/thread/MediaTransferFlvByJavacv.java index 1a375e7..eca8f02 100644 --- a/ruoyi-video/src/main/java/com/ruoyi/video/thread/MediaTransferFlvByJavacv.java +++ b/ruoyi-video/src/main/java/com/ruoyi/video/thread/MediaTransferFlvByJavacv.java @@ -5,7 +5,6 @@ import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.video.common.ClientType; import com.ruoyi.video.domain.dto.CameraDto; import com.ruoyi.video.service.MediaService; -import com.ruoyi.video.utils.ArcFaceEngineUtil; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; @@ -97,12 +96,10 @@ public class MediaTransferFlvByJavacv extends MediaTransfer implements Runnable */ private Thread listenThread; - private ArcFaceEngineUtil arcFaceEngineUtil; public MediaTransferFlvByJavacv(CameraDto cameraDto) { super(); this.cameraDto = cameraDto; - this.arcFaceEngineUtil = SpringUtils.getBean(ArcFaceEngineUtil.class); } public boolean isRunning() { @@ -129,45 +126,43 @@ public class MediaTransferFlvByJavacv extends MediaTransfer implements Runnable this.recorderStatus = recorderStatus; } - /** - * 创建拉流器 - * - * @return - */ protected boolean createGrabber() { - // 拉流器 grabber = new FFmpegFrameGrabber(cameraDto.getUrl()); - // 超时时间(15秒) - grabber.setOption("stimeout", cameraDto.getNetTimeout()); + + // 这些参数很多是“微秒”单位;如果你的 getNetTimeout() / getReadOrWriteTimeout() 是毫秒, + // 记得 *1000 转为微秒字符串。这里演示保守固定值,先跑通为先。 + String fiveSecUs = "5000000"; // 5s in microseconds + String oneMb = "1048576"; // 1MB + + // ---- 通用优化 ---- grabber.setOption("threads", "1"); - // grabber.setPixelFormat(avutil.AV_PIX_FMT_YUV420P); - // 设置缓存大小,提高画质、减少卡顿花屏 - grabber.setOption("buffer_size", "1024000"); + grabber.setOption("buffer_size", oneMb); // 输入缓冲 + grabber.setOption("rw_timeout", fiveSecUs); // 读写超时(微秒) + grabber.setOption("stimeout", fiveSecUs); // 套接字超时(微秒) + grabber.setOption("probesize", fiveSecUs); // 探测时长(微秒) + grabber.setOption("analyzeduration", fiveSecUs);// 解析时长(微秒) + grabber.setOption("fflags", "nobuffer"); // 低延迟 + grabber.setOption("flags", "low_delay"); + grabber.setOption("loglevel", "debug"); // 先开调试,稳定后可关 - // 读写超时,适用于所有协议的通用读写超时 - grabber.setOption("rw_timeout", cameraDto.getReadOrWriteTimeout()); - // 探测视频流信息,为空默认5000000微秒 - grabber.setOption("probesize", cameraDto.getReadOrWriteTimeout()); - // 解析视频流信息,为空默认5000000微秒 - grabber.setOption("analyzeduration", cameraDto.getReadOrWriteTimeout()); - - // 如果为rtsp流,增加配置 - if ("rtsp".equals(cameraDto.getUrl().substring(0, 4))) { - // 设置打开协议tcp / udp + // ---- RTSP 关键修正 ---- + if (cameraDto.getUrl().toLowerCase().startsWith("rtsp://")) { + // 统一走 TCP(RTP over RTSP/interleaved),避免 461 / NAT / 防火墙问题 grabber.setOption("rtsp_transport", "tcp"); - // 首选TCP进行RTP传输 - grabber.setOption("rtsp_flags", "prefer_tcp"); - - } else if ("rtmp".equals(cameraDto.getUrl().substring(0, 4))) { - // rtmp拉流缓冲区,默认3000毫秒 + // 不再设置 prefer_tcp(没意义了);确保不带 multicast 相关 flag + // 只要视频轨,避免音频轨导致的协商失败(可按需移除) + grabber.setOption("allowed_media_types", "video"); + // 避免较大的 RTP 乱序延迟 + grabber.setOption("max_delay", "500000"); // 0.5s (微秒) + // 某些设备对 UA 比较挑,设置一个常见 UA(可选) + grabber.setOption("user_agent", "Lavf/60"); + } + else if (cameraDto.getUrl().toLowerCase().startsWith("rtmp://")) { grabber.setOption("rtmp_buffer", "1000"); - // 默认rtmp流为直播模式,不允许seek - // grabber.setOption("rtmp_live", "live"); - - } else if ("desktop".equals(cameraDto.getUrl())) { - // 支持本地屏幕采集,可以用于监控屏幕、局域网和wifi投屏等 + } + else if ("desktop".equalsIgnoreCase(cameraDto.getUrl())) { grabber.setFormat("gdigrab"); - grabber.setOption("draw_mouse", "1");// 绘制鼠标 + grabber.setOption("draw_mouse", "1"); grabber.setNumBuffers(0); grabber.setOption("fflags", "nobuffer"); grabber.setOption("framerate", "25"); @@ -176,16 +171,16 @@ public class MediaTransferFlvByJavacv extends MediaTransfer implements Runnable try { grabber.start(); - log.info("\r\n{}\r\n启动拉流器成功", cameraDto.getUrl()); - return grabberStatus = true; + log.info("\n{}\n启动拉流器成功", cameraDto.getUrl()); + return (grabberStatus = true); } catch (FrameGrabber.Exception e) { MediaService.cameras.remove(cameraDto.getMediaKey()); - log.error("\r\n{}\r\n启动拉流器失败,网络超时或视频源不可用", cameraDto.getUrl()); -// e.printStackTrace(); + log.error("\n{}\n启动拉流器失败,网络超时或视频源不可用({})", cameraDto.getUrl(), e.getMessage()); + return (grabberStatus = false); } - return grabberStatus = false; } + /** * 创建转码推流录制器 * @@ -333,7 +328,7 @@ public class MediaTransferFlvByJavacv extends MediaTransfer implements Runnable if (frame != null) { - processFaceDetection(frame); + if (startTime == 0) { startTime = System.currentTimeMillis(); @@ -383,16 +378,6 @@ public class MediaTransferFlvByJavacv extends MediaTransfer implements Runnable log.info("关闭媒体流-javacv,{} ", cameraDto.getUrl()); } - @Async - public void processFaceDetection(Frame frame) { - if (arcFaceEngineUtil != null) { - ImageInfo imageInfo = arcFaceEngineUtil.frameToImageInfo(frame); - if (imageInfo != null) { - arcFaceEngineUtil.getUserInfo(imageInfo); - } - } - } - /** * 发送帧数据 * diff --git a/ruoyi-video/src/main/java/com/ruoyi/video/utils/ArcFaceEngineUtil.java b/ruoyi-video/src/main/java/com/ruoyi/video/utils/ArcFaceEngineUtil.java deleted file mode 100644 index 904b853..0000000 --- a/ruoyi-video/src/main/java/com/ruoyi/video/utils/ArcFaceEngineUtil.java +++ /dev/null @@ -1,286 +0,0 @@ -package com.ruoyi.video.utils; - -import com.arcsoft.face.*; -import com.arcsoft.face.enums.DetectMode; -import com.arcsoft.face.enums.DetectOrient; -import com.arcsoft.face.enums.ErrorInfo; -import com.arcsoft.face.enums.ImageFormat; -import com.arcsoft.face.toolkit.ImageFactory; -import com.arcsoft.face.toolkit.ImageInfo; -import com.ruoyi.common.config.RuoYiConfig; -import com.ruoyi.common.utils.file.FileUploadUtils; -import com.ruoyi.video.domain.VImage; -import com.ruoyi.video.service.IVImageService; -import jakarta.annotation.PostConstruct; -import lombok.extern.slf4j.Slf4j; -import org.bytedeco.javacv.Frame; -import org.bytedeco.javacv.Java2DFrameConverter; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; -import org.springframework.web.multipart.MultipartFile; - -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; -import java.awt.image.DataBufferByte; -import java.io.File; -import java.io.IOException; -import java.io.PrintStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.concurrent.atomic.AtomicLong; - -/** - * @Author: orange - * @CreateTime: 2025-01-16 - */ -@Slf4j -@Component -public class ArcFaceEngineUtil { - - @Value("${arcFace.appId}") - public String appId; - - @Value("${arcFace.sdkKey}") - public String sdkKey; - - int errorCode; - - String projectPath = System.getProperty("user.dir"); - - @Autowired - private IVImageService ivImageService; - - FaceEngine faceEngine = new FaceEngine(projectPath+"\\ruoyi-video\\src\\main\\resources\\libs\\WIN64"); - - private final AtomicLong lastSaveTime = new AtomicLong(0); // 记录上次保存的时间戳 - - private static final long SAVE_INTERVAL = 10 * 1000; // 10 秒的间隔 - - /** - * 初始化 - */ - @PostConstruct - public void init() { - this.errorCode = this.faceEngine.activeOnline(this.appId, this.sdkKey); - if (this.errorCode != ErrorInfo.MOK.getValue() && this.errorCode != ErrorInfo.MERR_ASF_ALREADY_ACTIVATED.getValue()) { - log.error("引擎激活失败"); - } - - ActiveFileInfo activeFileInfo = new ActiveFileInfo(); - this.errorCode = this.faceEngine.getActiveFileInfo(activeFileInfo); - if (this.errorCode != ErrorInfo.MOK.getValue() && this.errorCode != ErrorInfo.MERR_ASF_ALREADY_ACTIVATED.getValue()) { - log.error("获取激活文件信息失败"); - } - - EngineConfiguration engineConfiguration = new EngineConfiguration(); - engineConfiguration.setDetectMode(DetectMode.ASF_DETECT_MODE_IMAGE); - engineConfiguration.setDetectFaceOrientPriority(DetectOrient.ASF_OP_ALL_OUT); - engineConfiguration.setDetectFaceMaxNum(10); - engineConfiguration.setDetectFaceScaleVal(16); - FunctionConfiguration functionConfiguration = new FunctionConfiguration(); - functionConfiguration.setSupportAge(true); - functionConfiguration.setSupportFace3dAngle(true); - functionConfiguration.setSupportFaceDetect(true); - functionConfiguration.setSupportFaceRecognition(true); - functionConfiguration.setSupportGender(true); - functionConfiguration.setSupportLiveness(true); - functionConfiguration.setSupportIRLiveness(true); - engineConfiguration.setFunctionConfiguration(functionConfiguration); - this.errorCode = this.faceEngine.init(engineConfiguration); - if (this.errorCode != ErrorInfo.MOK.getValue()) { - log.error("初始化引擎失败"); - } - - log.info("------------------------"); - log.info("ruoyi-video初始化引擎成功"); - log.info("微信: chenbai0511"); - log.info("请我喝杯咖啡吧qwq"); - log.info("------------------------"); - - } - - /** - * Frame对象转换为ImageInfo对象 - * @param frame - * @return - */ - public ImageInfo frameToImageInfo(Frame frame) { - if (frame == null || frame.image == null) { - log.error("Frame是空或非图像帧------"); - return null; - } else { - Java2DFrameConverter converter = new Java2DFrameConverter(); - BufferedImage image = converter.convert(frame); - if (image == null) { - log.error("Converted image is null"); - return null; - } else { - int width = image.getWidth(); - int height = image.getHeight(); - byte[] imageData = ((DataBufferByte)image.getRaster().getDataBuffer()).getData(); - ImageInfo imageInfo = new ImageInfo(); - imageInfo.setWidth(width); - imageInfo.setHeight(height); - imageInfo.setImageFormat(ImageFormat.CP_PAF_BGR24); - imageInfo.setImageData(imageData); - return imageInfo; - } - } - } - - /** - * 虹软检测代码 - * @param imageInfo - * @return - */ - public HashMap getUserInfo(ImageInfo imageInfo) { - ImageInfo imageInfoFc = ImageFactory.getRGBData(new File("D:\\cat\\fc.jpg")); - List faceInfoListFc = new ArrayList(); - this.faceEngine.detectFaces(imageInfoFc.getImageData(), imageInfoFc.getWidth(), imageInfoFc.getHeight(), imageInfoFc.getImageFormat(), faceInfoListFc); - if (faceInfoListFc.isEmpty()) { - log.error("未检测到人脸(faceInfoListFc)"); - return null; - } else { - FaceFeature faceFeatureFc = new FaceFeature(); - this.faceEngine.extractFaceFeature(imageInfoFc.getImageData(), imageInfoFc.getWidth(), imageInfoFc.getHeight(), imageInfoFc.getImageFormat(), (FaceInfo)faceInfoListFc.get(0), faceFeatureFc); - List faceInfoList = new ArrayList(); - this.errorCode = this.faceEngine.detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList); - if (faceInfoList.isEmpty()) { - log.error("未检测到人脸(faceInfoList)"); - return null; - } else { - FaceFeature faceFeature = new FaceFeature(); - this.faceEngine.extractFaceFeature(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), (FaceInfo)faceInfoList.get(0), faceFeature); - FaceFeature targetFaceFeature = new FaceFeature(); - targetFaceFeature.setFeatureData(faceFeatureFc.getFeatureData()); - FaceFeature sourceFaceFeature = new FaceFeature(); - sourceFaceFeature.setFeatureData(faceFeature.getFeatureData()); - FaceSimilar faceSimilar = new FaceSimilar(); - this.faceEngine.compareFaceFeature(targetFaceFeature, sourceFaceFeature, faceSimilar); - System.out.println("相似度:" + faceSimilar.getScore()); - if (faceSimilar.getScore() < 0.8F) { - this.saveImage(imageInfo); - } - - this.errorCode = this.faceEngine.setLivenessParam(0.5F, 0.7F); - FunctionConfiguration configuration = new FunctionConfiguration(); - configuration.setSupportAge(true); - configuration.setSupportFace3dAngle(true); - configuration.setSupportGender(true); - configuration.setSupportLiveness(true); - this.errorCode = this.faceEngine.process(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList, configuration); - HashMap map = new HashMap(); - List genderInfoList = new ArrayList(); - this.errorCode = this.faceEngine.getGender(genderInfoList); - if (!genderInfoList.isEmpty()) { - System.out.println("性别:" + ((GenderInfo)genderInfoList.get(0)).getGender()); - map.put("gender", ((GenderInfo)genderInfoList.get(0)).getGender()); - } else { - log.error("未获取到性别信息"); - } - - List ageInfoList = new ArrayList(); - this.errorCode = this.faceEngine.getAge(ageInfoList); - if (!ageInfoList.isEmpty()) { - System.out.println("年龄:" + ((AgeInfo)ageInfoList.get(0)).getAge()); - map.put("age", ((AgeInfo)ageInfoList.get(0)).getAge()); - } else { - log.error("未获取到年龄信息"); - } - - List face3DAngleList = new ArrayList(); - this.errorCode = this.faceEngine.getFace3DAngle(face3DAngleList); - if (!face3DAngleList.isEmpty()) { - PrintStream var10000 = System.out; - float var10001 = ((Face3DAngle)face3DAngleList.get(0)).getPitch(); - var10000.println("3D角度:" + var10001 + "," + ((Face3DAngle)face3DAngleList.get(0)).getRoll() + "," + ((Face3DAngle)face3DAngleList.get(0)).getYaw()); - map.put("3DAngle", face3DAngleList.get(0)); - } else { - log.error("未获取到3D角度信息"); - } - - List livenessInfoList = new ArrayList(); - this.errorCode = this.faceEngine.getLiveness(livenessInfoList); - if (!livenessInfoList.isEmpty()) { - System.out.println("活体:" + ((LivenessInfo)livenessInfoList.get(0)).getLiveness()); - map.put("liveness", ((LivenessInfo)livenessInfoList.get(0)).getLiveness()); - } else { - log.error("未获取到活体信息"); - } - - return map; - } - } - } - - /** - * 保存图片 - * @param imageInfo - */ - private void saveImage(ImageInfo imageInfo) { - long currentTime = System.currentTimeMillis(); - long lastTime = lastSaveTime.get(); - - if (currentTime - lastTime < SAVE_INTERVAL) { - log.info("10 秒内已保存过照片,跳过本次保存"); - return; - } - - if (!lastSaveTime.compareAndSet(lastTime, currentTime)) { - return; - } - - try { - File dir = new File("D:\\cat"); - if (!dir.exists()) { - dir.mkdirs(); - } - String fileName = "image_" + currentTime + ".jpg"; - File outputFile = new File(dir, fileName); - BufferedImage bufferedImage = this.convertImageInfoToBufferedImage(imageInfo); - if (bufferedImage != null) { - ImageIO.write(bufferedImage, "jpg", outputFile); - log.info("图片已保存:{}", outputFile.getAbsolutePath()); - String filePath = RuoYiConfig.getUploadPath(); - MultipartFile multipartFile = new CustomMultipartFile(outputFile, "image/jpeg"); - String file_name = FileUploadUtils.upload(filePath, multipartFile); - VImage vImage = new VImage(); - vImage.setImageName(file_name); - vImage.setImageData(fileName); - this.ivImageService.insertVImage(vImage); - } else { - log.error("无法转换 ImageInfo 为 BufferedImage"); - } - } catch (IOException var16) { - log.error("保存图片失败:{}", var16.getMessage(), var16); - } - } - - /** - * 将ImageInfo转换为BufferedImage - * @param imageInfo - * @return - */ - private BufferedImage convertImageInfoToBufferedImage(ImageInfo imageInfo) { - if (imageInfo != null && imageInfo.getImageData() != null) { - if (imageInfo.getImageFormat() == ImageFormat.CP_PAF_BGR24) { - int width = imageInfo.getWidth(); - int height = imageInfo.getHeight(); - byte[] imageData = imageInfo.getImageData(); - BufferedImage bufferedImage = new BufferedImage(width, height, 5); - byte[] targetPixels = ((DataBufferByte)bufferedImage.getRaster().getDataBuffer()).getData(); - System.arraycopy(imageData, 0, targetPixels, 0, imageData.length); - return bufferedImage; - } else { - log.error("不支持的图像格式:{}", imageInfo.getImageFormat()); - return null; - } - } else { - log.error("ImageInfo 或 imageData 为空"); - return null; - } - } - -} diff --git a/sql/ry_face.sql b/sql/ry_face.sql index f1721e3..b882ea8 100644 --- a/sql/ry_face.sql +++ b/sql/ry_face.sql @@ -13,7 +13,8 @@ Date: 16/01/2025 17:33:13 */ - +CREATE DATABASE IF NOT EXISTS `fad_watch` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; +USE `fad_watch`; SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0;