改为 flv 格式 实时流下载 不再需要临时文件转储
This commit is contained in:
parent
0bcebc8c30
commit
951bffea4e
@ -1,23 +1,22 @@
|
|||||||
package cn.skcks.docking.gb28181.wvp.service.video;
|
package cn.skcks.docking.gb28181.wvp.service.video;
|
||||||
|
|
||||||
import cn.hutool.core.io.IoUtil;
|
|
||||||
import cn.hutool.core.util.IdUtil;
|
|
||||||
import jakarta.servlet.AsyncContext;
|
import jakarta.servlet.AsyncContext;
|
||||||
import jakarta.servlet.ServletResponse;
|
import jakarta.servlet.ServletResponse;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.bytedeco.ffmpeg.avcodec.AVPacket;
|
||||||
import org.bytedeco.ffmpeg.global.avcodec;
|
import org.bytedeco.ffmpeg.global.avcodec;
|
||||||
import org.bytedeco.ffmpeg.global.avutil;
|
import org.bytedeco.ffmpeg.global.avutil;
|
||||||
import org.bytedeco.javacv.FFmpegFrameGrabber;
|
import org.bytedeco.javacv.FFmpegFrameGrabber;
|
||||||
import org.bytedeco.javacv.FFmpegFrameRecorder;
|
import org.bytedeco.javacv.FFmpegFrameRecorder;
|
||||||
import org.bytedeco.javacv.Frame;
|
import org.bytedeco.javacv.FFmpegLogCallback;
|
||||||
import org.bytedeco.javacv.FrameGrabber;
|
import org.bytedeco.javacv.FrameGrabber;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.nio.file.Path;
|
import java.io.OutputStream;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@ -27,7 +26,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
@Service
|
@Service
|
||||||
public class RecordService {
|
public class RecordService {
|
||||||
public void header(HttpServletResponse response) {
|
public void header(HttpServletResponse response) {
|
||||||
response.setContentType("video/mp4");
|
response.setContentType("video/x-flv");
|
||||||
response.setHeader("Accept-Ranges", "none");
|
response.setHeader("Accept-Ranges", "none");
|
||||||
response.setHeader("Connection", "close");
|
response.setHeader("Connection", "close");
|
||||||
}
|
}
|
||||||
@ -38,47 +37,68 @@ public class RecordService {
|
|||||||
asyncContext.setTimeout(0);
|
asyncContext.setTimeout(0);
|
||||||
asyncContext.start(() -> {
|
asyncContext.start(() -> {
|
||||||
header(response);
|
header(response);
|
||||||
record(asyncContext.getResponse(), url, time, 60);
|
try{
|
||||||
log.info("record 结束");
|
record(asyncContext.getResponse(), url, time);
|
||||||
asyncContext.complete();
|
} finally {
|
||||||
|
log.info("record 结束");
|
||||||
|
asyncContext.complete();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public void record(ServletResponse response, String url, long time ,long timeout) {
|
public void record(ServletResponse response, String url, long time) {
|
||||||
// response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
|
// response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
|
||||||
Path tmp = Path.of(System.getProperty("java.io.tmpdir"), IdUtil.getSnowflakeNextIdStr()).toAbsolutePath();
|
// Path tmp = Path.of(System.getProperty("java.io.tmpdir"), IdUtil.getSnowflakeNextIdStr()).toAbsolutePath();
|
||||||
File file = new File(tmp + ".mp4");
|
// File file = new File(tmp + ".mp4");
|
||||||
log.info("创建文件 {}, {}", file, file.createNewFile());
|
// log.info("创建文件 {}, {}", file, file.createNewFile());
|
||||||
|
|
||||||
|
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||||
|
OutputStream outputStream = response.getOutputStream();
|
||||||
log.info("url {}", url);
|
log.info("url {}", url);
|
||||||
|
FFmpegLogCallback.set();
|
||||||
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(url);
|
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(url);
|
||||||
grabber.start();
|
grabber.start();
|
||||||
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(file, grabber.getImageWidth(), grabber.getImageHeight(), grabber.getAudioChannels());
|
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(stream, grabber.getImageWidth(), grabber.getImageHeight(), grabber.getAudioChannels());
|
||||||
recorder.start();
|
// log.info("{}", file);
|
||||||
log.info("开始录像");
|
recorder.setInterleaved(true);
|
||||||
log.info("{}", file);
|
recorder.setVideoOption("preset", "ultrafast");
|
||||||
|
recorder.setVideoOption("tune", "zerolatency");
|
||||||
|
recorder.setVideoOption("crf", "25");
|
||||||
|
recorder.setFrameRate(grabber.getFrameRate());
|
||||||
|
recorder.setSampleRate(grabber.getSampleRate());
|
||||||
|
recorder.setOption("flvflags","no_duration_filesize");
|
||||||
|
if (grabber.getAudioChannels() > 0) {
|
||||||
|
recorder.setAudioChannels(grabber.getAudioChannels());
|
||||||
|
recorder.setAudioBitrate(grabber.getAudioBitrate());
|
||||||
|
recorder.setAudioCodec(grabber.getAudioCodec());
|
||||||
|
}
|
||||||
|
recorder.setVideoBitrate(grabber.getVideoBitrate());
|
||||||
|
// recorder.setVideoCodec(grabber.getVideoCodec());
|
||||||
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
|
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
|
||||||
recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P); // 视频源数据yuv
|
recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P); // 视频源数据yuv
|
||||||
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC); // 设置音频压缩方式
|
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC); // 设置音频压缩方式
|
||||||
recorder.setFormat("mp4");
|
recorder.setFormat("flv");
|
||||||
recorder.setVideoOption("threads", String.valueOf(Runtime.getRuntime().availableProcessors())); // 解码线程数
|
recorder.setVideoOption("threads", String.valueOf(Runtime.getRuntime().availableProcessors())); // 解码线程数
|
||||||
|
recorder.start(grabber.getFormatContext());
|
||||||
|
log.info("开始录像");
|
||||||
|
|
||||||
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
|
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
|
||||||
AtomicBoolean record = new AtomicBoolean(true);
|
AtomicBoolean record = new AtomicBoolean(true);
|
||||||
scheduledExecutorService.schedule(() -> {
|
scheduledExecutorService.schedule(() -> {
|
||||||
log.info("到达超时时间, 结束录制");
|
log.info("到达结束时间, 结束录制");
|
||||||
record.set(false);
|
record.set(false);
|
||||||
}, time + timeout, TimeUnit.SECONDS);
|
}, time, TimeUnit.SECONDS);
|
||||||
long maxTime = TimeUnit.SECONDS.toMicros(time);
|
|
||||||
try {
|
try {
|
||||||
Frame frame;
|
AVPacket k;
|
||||||
while (!recorder.isCloseOutputStream() && record.get() && (frame = grabber.grab()) != null) {
|
while (record.get() && (k = grabber.grabPacket()) != null) {
|
||||||
long micros = TimeUnit.MICROSECONDS.toMicros(recorder.getTimestamp());
|
recorder.recordPacket(k);
|
||||||
if(micros >= maxTime){
|
if(stream.size() > 0){
|
||||||
break;
|
outputStream.write(stream.toByteArray());
|
||||||
|
outputStream.flush();
|
||||||
|
stream.reset();
|
||||||
}
|
}
|
||||||
recorder.record(frame);
|
avcodec.av_packet_unref(k);
|
||||||
}
|
}
|
||||||
grabber.close();
|
grabber.close();
|
||||||
recorder.close();
|
recorder.close();
|
||||||
@ -87,15 +107,6 @@ public class RecordService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.info("结束录制");
|
log.info("结束录制");
|
||||||
InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
|
|
||||||
OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
|
|
||||||
try {
|
|
||||||
IoUtil.copy(inputStream, outputStream);
|
|
||||||
} catch (Exception ignore) {
|
|
||||||
}
|
|
||||||
log.info("临时文件 {} 写入 响应 完成", file);
|
|
||||||
log.info("删除临时文件 {} {}", file, file.delete());
|
|
||||||
outputStream.close();
|
outputStream.close();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user