视频录制
This commit is contained in:
parent
4389cfda43
commit
0c62fa0584
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -39,6 +39,11 @@
|
|||||||
<artifactId>spring-boot-starter-web</artifactId>
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.servlet</groupId>
|
||||||
|
<artifactId>jakarta.servlet-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.cloud</groupId>
|
<groupId>org.springframework.cloud</groupId>
|
||||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user