From 0c62fa05849eff13724b5a6d1800632a254a24ea Mon Sep 17 00:00:00 2001
From: shikong <919411476@qq.com>
Date: Wed, 6 Sep 2023 16:40:42 +0800
Subject: [PATCH] =?UTF-8?q?=E8=A7=86=E9=A2=91=E5=BD=95=E5=88=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../wvp/api/video/RecordController.java | 23 ++++++
gb28181-wvp-proxy-service/pom.xml | 5 ++
.../wvp/executor/DefaultVideoExecutor.java | 55 ++++++++++++++
.../wvp/service/video/RecordService.java | 75 +++++++++++++++++++
4 files changed, 158 insertions(+)
create mode 100644 gb28181-wvp-proxy-api/src/main/java/cn/skcks/docking/gb28181/wvp/api/video/RecordController.java
create mode 100644 gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/executor/DefaultVideoExecutor.java
create mode 100644 gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/video/RecordService.java
diff --git a/gb28181-wvp-proxy-api/src/main/java/cn/skcks/docking/gb28181/wvp/api/video/RecordController.java b/gb28181-wvp-proxy-api/src/main/java/cn/skcks/docking/gb28181/wvp/api/video/RecordController.java
new file mode 100644
index 0000000..57fe2d7
--- /dev/null
+++ b/gb28181-wvp-proxy-api/src/main/java/cn/skcks/docking/gb28181/wvp/api/video/RecordController.java
@@ -0,0 +1,23 @@
+package cn.skcks.docking.gb28181.wvp.api.video;
+
+import cn.skcks.docking.gb28181.wvp.service.video.RecordService;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@Slf4j
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/video/record")
+public class RecordController {
+ private final RecordService recordService;
+
+ @GetMapping
+ public void record(HttpServletResponse response, @RequestParam String url,@RequestParam long time){
+ recordService.record(response,url,time);
+ }
+}
diff --git a/gb28181-wvp-proxy-service/pom.xml b/gb28181-wvp-proxy-service/pom.xml
index 760c670..84e8fdb 100644
--- a/gb28181-wvp-proxy-service/pom.xml
+++ b/gb28181-wvp-proxy-service/pom.xml
@@ -39,6 +39,11 @@
spring-boot-starter-web
+
+ jakarta.servlet
+ jakarta.servlet-api
+
+
org.springframework.cloud
spring-cloud-starter-openfeign
diff --git a/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/executor/DefaultVideoExecutor.java b/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/executor/DefaultVideoExecutor.java
new file mode 100644
index 0000000..e077b31
--- /dev/null
+++ b/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/executor/DefaultVideoExecutor.java
@@ -0,0 +1,55 @@
+package cn.skcks.docking.gb28181.wvp.executor;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.*;
+
+@Configuration
+@Order(1)
+@EnableAsync(proxyTargetClass = true)
+public class DefaultVideoExecutor {
+ /**
+ * cpu 核心数
+ */
+ public static final int CPU_NUM = Runtime.getRuntime().availableProcessors();
+ /**
+ * 最大线程数
+ */
+ public static final int MAX_POOL_SIZE = CPU_NUM * 2;
+ /**
+ * 允许线程空闲时间(单位:默认为秒)
+ */
+ private static final int KEEP_ALIVE_TIME = 20;
+ /**
+ * 队列长度
+ */
+ public static final int TASK_NUM = 5;
+ /**
+ * 线程名称(前缀)
+ */
+ public static final String THREAD_NAME_PREFIX = "video-executor";
+
+ public static final String EXECUTOR_BEAN_NAME = "videoTaskExecutor";
+
+
+ @Bean(EXECUTOR_BEAN_NAME)
+ public Executor videoTaskExecutor(){
+ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+ executor.setCorePoolSize(CPU_NUM);
+ executor.setMaxPoolSize(MAX_POOL_SIZE);
+ executor.setQueueCapacity(TASK_NUM);
+ executor.setKeepAliveSeconds(KEEP_ALIVE_TIME);
+ executor.setThreadNamePrefix(THREAD_NAME_PREFIX);
+
+ // 线程池对拒绝任务的处理策略
+ // CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务
+ executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+ // 初始化
+ executor.initialize();
+ return executor;
+ }
+}
diff --git a/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/video/RecordService.java b/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/video/RecordService.java
new file mode 100644
index 0000000..b28fe74
--- /dev/null
+++ b/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/video/RecordService.java
@@ -0,0 +1,75 @@
+package cn.skcks.docking.gb28181.wvp.service.video;
+
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.IdUtil;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.bytedeco.ffmpeg.global.avcodec;
+import org.bytedeco.ffmpeg.global.avutil;
+import org.bytedeco.javacv.*;
+import org.springframework.stereotype.Service;
+
+import java.io.*;
+import java.nio.file.Path;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+@Slf4j
+@Service
+public class RecordService {
+
+ @SneakyThrows
+ public void record(HttpServletResponse response, String url, long timeout){
+ response.reset();
+ // response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
+ response.setContentType("video/mp4");
+
+ Path tmp = Path.of(System.getProperty("java.io.tmpdir"), IdUtil.getSnowflakeNextIdStr()).toAbsolutePath();
+ File file = new File(tmp + ".mp4");
+ log.info("创建文件 {}, {}", file, file.createNewFile());
+
+ log.info("url {}", url);
+ try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(url)) {
+ grabber.start();
+ try(FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(file, grabber.getImageWidth(), grabber.getImageHeight(),grabber.getAudioChannels())){
+ recorder.start();
+ log.info("开始录像");
+ log.info("{}", file);
+ recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
+ recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P); //视频源数据yuv
+ recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC); //设置音频压缩方式
+ recorder.setFormat("mp4");
+ recorder.setVideoOption("threads", String.valueOf(Runtime.getRuntime().availableProcessors())); //解码线程数
+
+ ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
+ AtomicBoolean record = new AtomicBoolean(true);
+ scheduledExecutorService.schedule(()->{
+ log.info("到达超时时间, 结束录制");
+ record.set(false);
+ }, timeout, TimeUnit.SECONDS);
+
+ try {
+ Frame frame;
+ while (record.get() && (frame = grabber.grab()) != null) {
+ recorder.record(frame);
+ }
+ grabber.stop();
+ grabber.release();
+ recorder.stop();
+ } catch (FFmpegFrameRecorder.Exception | FrameGrabber.Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ } finally {
+ log.info("结束录制");
+ InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
+ OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
+ IoUtil.copy(inputStream, outputStream);
+ log.info("临时文件 {} 写入 响应 完成", file);
+ log.info("删除临时文件 {} {}", file, file.delete());
+ }
+ }
+}