init
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
package com.ruoyi.video.thread;
|
||||
|
||||
/**
|
||||
* @Author: orange
|
||||
* @CreateTime: 2025-01-16
|
||||
*/
|
||||
public class MediaTransfer {
|
||||
public MediaTransfer() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,587 @@
|
||||
package com.ruoyi.video.thread;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.ruoyi.video.common.ClientType;
|
||||
import com.ruoyi.video.common.MediaConstant;
|
||||
import com.ruoyi.video.domain.dto.CameraDto;
|
||||
import com.ruoyi.video.service.MediaService;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.GenericFutureListener;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bytedeco.javacv.FrameGrabber;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
/**
|
||||
* @Author: orange
|
||||
* @CreateTime: 2025-01-16
|
||||
*/
|
||||
@Slf4j
|
||||
public class MediaTransferFlvByFFmpeg extends MediaTransfer {
|
||||
|
||||
/**
|
||||
* ws客户端
|
||||
*/
|
||||
private ConcurrentHashMap<String, ChannelHandlerContext> wsClients = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* http客户端
|
||||
*/
|
||||
private ConcurrentHashMap<String, ChannelHandlerContext> httpClients = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* flv header
|
||||
*/
|
||||
private byte[] header = null;
|
||||
/**
|
||||
* 相机
|
||||
*/
|
||||
private CameraDto cameraDto;
|
||||
|
||||
private List<String> command = new ArrayList<>();
|
||||
|
||||
private ServerSocket tcpServer = null;
|
||||
|
||||
private Process process;
|
||||
private Thread inputThread;
|
||||
private Thread errThread;
|
||||
private Thread outputThread;
|
||||
private Thread listenThread;
|
||||
private boolean running = false; // 启动
|
||||
private boolean enableLog = true;
|
||||
|
||||
private int hcSize, wcSize = 0;
|
||||
|
||||
// 记录当前
|
||||
long currentTimeMillis = System.currentTimeMillis();
|
||||
|
||||
/**
|
||||
* 用于没有客户端时候的计时
|
||||
*/
|
||||
private int noClient = 0;
|
||||
|
||||
public MediaTransferFlvByFFmpeg(final String executable) {
|
||||
command.add(executable);
|
||||
buildCommand();
|
||||
}
|
||||
|
||||
public MediaTransferFlvByFFmpeg(CameraDto cameraDto) {
|
||||
command.add(System.getProperty(MediaConstant.ffmpegPathKey));
|
||||
this.cameraDto = cameraDto;
|
||||
buildCommand();
|
||||
}
|
||||
|
||||
public MediaTransferFlvByFFmpeg(final String executable, CameraDto cameraDto) {
|
||||
command.add(executable);
|
||||
this.cameraDto = cameraDto;
|
||||
buildCommand();
|
||||
}
|
||||
|
||||
public MediaTransferFlvByFFmpeg(final String executable, CameraDto cameraDto, boolean enableLog) {
|
||||
command.add(executable);
|
||||
this.cameraDto = cameraDto;
|
||||
this.enableLog = enableLog;
|
||||
buildCommand();
|
||||
}
|
||||
|
||||
public boolean isEnableLog() {
|
||||
return enableLog;
|
||||
}
|
||||
|
||||
public void setEnableLog(boolean enableLog) {
|
||||
this.enableLog = enableLog;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return running;
|
||||
}
|
||||
|
||||
public void setRunning(boolean running) {
|
||||
this.running = running;
|
||||
}
|
||||
|
||||
private MediaTransferFlvByFFmpeg addArgument(String argument) {
|
||||
command.add(argument);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建ffmpeg命令
|
||||
*/
|
||||
private void buildCommand() {
|
||||
this.addArgument("-rtsp_transport").addArgument("tcp").addArgument("-i").addArgument(cameraDto.getUrl())
|
||||
.addArgument("-max_delay").addArgument("1")
|
||||
// .addArgument("-strict").addArgument("experimental")
|
||||
.addArgument("-g").addArgument("25").addArgument("-r").addArgument("25")
|
||||
// .addArgument("-b").addArgument("200000")
|
||||
// .addArgument("-filter_complex").addArgument("setpts='(RTCTIME - RTCSTART) / (TB * 1000000)'")
|
||||
.addArgument("-c:v").addArgument("libx264").addArgument("-preset:v").addArgument("ultrafast")
|
||||
// .addArgument("-preset:v").addArgument("fast")
|
||||
.addArgument("-tune:v").addArgument("zerolatency")
|
||||
// .addArgument("-crf").addArgument("26")
|
||||
.addArgument("-c:a").addArgument("aac")
|
||||
// .addArgument("-qmin").addArgument("28")
|
||||
// .addArgument("-qmax").addArgument("32")
|
||||
// .addArgument("-b:v").addArgument("448k")
|
||||
// .addArgument("-b:a").addArgument("64k")
|
||||
.addArgument("-f").addArgument("flv");
|
||||
}
|
||||
|
||||
// private void buildCommand() {
|
||||
// this
|
||||
//// .addArgument("-rtsp_transport").addArgument("tcp")
|
||||
// .addArgument("-i").addArgument(camera.getUrl())
|
||||
// .addArgument("-max_delay").addArgument("100")
|
||||
//// .addArgument("-strict").addArgument("experimental")
|
||||
// .addArgument("-g").addArgument("10")
|
||||
//// .addArgument("-r").addArgument("25")
|
||||
//// .addArgument("-b").addArgument("200000")
|
||||
//// .addArgument("-filter_complex").addArgument("setpts='(RTCTIME - RTCSTART) / (TB * 1000000)'")
|
||||
// .addArgument("-c:v").addArgument("libx264")
|
||||
// .addArgument("-preset:v").addArgument("ultrafast")
|
||||
// .addArgument("-tune:v").addArgument("zerolatency")
|
||||
//// .addArgument("-crf").addArgument("26")
|
||||
// .addArgument("-c:a").addArgument("aac")
|
||||
// .addArgument("-qmin").addArgument("28")
|
||||
// .addArgument("-qmax").addArgument("32")
|
||||
// .addArgument("-b:v").addArgument("448k")
|
||||
// .addArgument("-b:a").addArgument("64k")
|
||||
// .addArgument("-f").addArgument("flv");
|
||||
// }
|
||||
|
||||
/**
|
||||
* 执行推流
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public MediaTransferFlvByFFmpeg execute() {
|
||||
String output = getOutput();
|
||||
command.add(output);
|
||||
|
||||
String join = CollUtil.join(command, " ");
|
||||
log.info(join);
|
||||
try {
|
||||
process = new ProcessBuilder(command).start();
|
||||
running = true;
|
||||
listenNetTimeout();
|
||||
dealStream(process);
|
||||
outputData();
|
||||
listenClient();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* flv数据
|
||||
*/
|
||||
private void outputData() {
|
||||
outputThread = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
Socket client = null;
|
||||
try {
|
||||
client = tcpServer.accept();
|
||||
DataInputStream input = new DataInputStream(client.getInputStream());
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
int len = 0;
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
while (running) {
|
||||
|
||||
len = input.read(buffer);
|
||||
if (len == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
bos.write(buffer, 0, len);
|
||||
|
||||
if (header == null) {
|
||||
header = bos.toByteArray();
|
||||
// System.out.println(HexUtil.encodeHexStr(header));
|
||||
bos.reset();
|
||||
continue;
|
||||
}
|
||||
|
||||
// 帧数据
|
||||
byte[] data = bos.toByteArray();
|
||||
bos.reset();
|
||||
|
||||
// 发送到前端
|
||||
sendFrameData(data);
|
||||
}
|
||||
|
||||
try {
|
||||
client.close();
|
||||
} catch (java.lang.Exception e) {
|
||||
}
|
||||
try {
|
||||
input.close();
|
||||
} catch (java.lang.Exception e) {
|
||||
}
|
||||
try {
|
||||
bos.close();
|
||||
} catch (java.lang.Exception e) {
|
||||
}
|
||||
|
||||
log.info("关闭媒体流-ffmpeg,{} ", cameraDto.getUrl());
|
||||
|
||||
} catch (SocketTimeoutException e1) {
|
||||
// e1.printStackTrace();
|
||||
// 超时关闭
|
||||
} catch (IOException e) {
|
||||
// e.printStackTrace();
|
||||
} finally {
|
||||
MediaService.cameras.remove(cameraDto.getMediaKey());
|
||||
running = false;
|
||||
process.destroy();
|
||||
try {
|
||||
if (null != client) {
|
||||
client.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
}
|
||||
try {
|
||||
if (null != tcpServer) {
|
||||
tcpServer.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
outputThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听客户端
|
||||
*/
|
||||
public void listenClient() {
|
||||
listenThread = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
while (running) {
|
||||
hasClient();
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
listenThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听网络异常超时
|
||||
*/
|
||||
public void listenNetTimeout() {
|
||||
Thread listenNetTimeoutThread = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
while (true) {
|
||||
|
||||
if ((System.currentTimeMillis() - currentTimeMillis) > 15000) {
|
||||
log.info("网络异常超时");
|
||||
MediaService.cameras.remove(cameraDto.getMediaKey());
|
||||
stopFFmpeg();
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(5000);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
listenNetTimeoutThread.setDaemon(true);
|
||||
listenNetTimeoutThread.start();
|
||||
}
|
||||
|
||||
public static MediaTransferFlvByFFmpeg atPath() {
|
||||
return atPath(null);
|
||||
}
|
||||
|
||||
public static MediaTransferFlvByFFmpeg atPath(final String absPath) {
|
||||
final String executable;
|
||||
if (absPath != null) {
|
||||
executable = absPath;
|
||||
} else {
|
||||
executable = System.getProperty(MediaConstant.ffmpegPathKey);
|
||||
}
|
||||
return new MediaTransferFlvByFFmpeg(executable);
|
||||
}
|
||||
|
||||
/**
|
||||
* 控制台输出
|
||||
*
|
||||
* @param process
|
||||
*/
|
||||
private void dealStream(Process process) {
|
||||
if (process == null) {
|
||||
return;
|
||||
}
|
||||
// 处理InputStream的线程
|
||||
inputThread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
String line = null;
|
||||
try {
|
||||
while (running) {
|
||||
line = in.readLine();
|
||||
currentTimeMillis = System.currentTimeMillis();
|
||||
if (line == null) {
|
||||
break;
|
||||
}
|
||||
if (enableLog) {
|
||||
log.info("output: " + line);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
running = false;
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// 处理ErrorStream的线程
|
||||
errThread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream()));
|
||||
String line = null;
|
||||
try {
|
||||
while (running) {
|
||||
line = err.readLine();
|
||||
currentTimeMillis = System.currentTimeMillis();
|
||||
if (line == null) {
|
||||
break;
|
||||
}
|
||||
if (enableLog) {
|
||||
log.info("err: " + line);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
running = false;
|
||||
err.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
inputThread.start();
|
||||
errThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出到tcp
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private String getOutput() {
|
||||
try {
|
||||
tcpServer = new ServerSocket(0, 1, InetAddress.getLoopbackAddress());
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append("tcp://");
|
||||
sb.append(tcpServer.getInetAddress().getHostAddress());
|
||||
sb.append(":");
|
||||
sb.append(tcpServer.getLocalPort());
|
||||
tcpServer.setSoTimeout(10000);
|
||||
return sb.toString();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
new RuntimeException("无法启用端口");
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭
|
||||
*/
|
||||
public void stopFFmpeg() {
|
||||
this.running = false;
|
||||
try {
|
||||
this.process.destroy();
|
||||
log.info("关闭媒体流-ffmpeg,{} ", cameraDto.getUrl());
|
||||
} catch (java.lang.Exception e) {
|
||||
process.destroyForcibly();
|
||||
}
|
||||
|
||||
// 媒体异常时,主动断开前端长连接
|
||||
for (Map.Entry<String, ChannelHandlerContext> entry : wsClients.entrySet()) {
|
||||
try {
|
||||
entry.getValue().close();
|
||||
} catch (java.lang.Exception e) {
|
||||
} finally {
|
||||
wsClients.remove(entry.getKey());
|
||||
}
|
||||
}
|
||||
for (Map.Entry<String, ChannelHandlerContext> entry : httpClients.entrySet()) {
|
||||
try {
|
||||
entry.getValue().close();
|
||||
} catch (java.lang.Exception e) {
|
||||
} finally {
|
||||
httpClients.remove(entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭流
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public void hasClient() {
|
||||
|
||||
int newHcSize = httpClients.size();
|
||||
int newWcSize = wsClients.size();
|
||||
if (hcSize != newHcSize || wcSize != newWcSize) {
|
||||
hcSize = newHcSize;
|
||||
wcSize = newWcSize;
|
||||
log.info("\r\n{}\r\nhttp连接数:{}, ws连接数:{} \r\n", cameraDto.getUrl(), newHcSize, newWcSize);
|
||||
}
|
||||
|
||||
// 无需自动关闭
|
||||
if (!cameraDto.isAutoClose()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (httpClients.isEmpty() && wsClients.isEmpty()) {
|
||||
// 等待20秒还没有客户端,则关闭推流
|
||||
if (noClient > cameraDto.getNoClientsDuration()) {
|
||||
running = false;
|
||||
MediaService.cameras.remove(cameraDto.getMediaKey());
|
||||
} else {
|
||||
noClient += 1000;
|
||||
}
|
||||
} else {
|
||||
// 重置计时
|
||||
noClient = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送帧数据
|
||||
*
|
||||
* @param data
|
||||
*/
|
||||
private void sendFrameData(byte[] data) {
|
||||
// ws
|
||||
for (Map.Entry<String, ChannelHandlerContext> entry : wsClients.entrySet()) {
|
||||
try {
|
||||
if (entry.getValue().channel().isWritable()) {
|
||||
entry.getValue().writeAndFlush(new BinaryWebSocketFrame(Unpooled.copiedBuffer(data)));
|
||||
} else {
|
||||
wsClients.remove(entry.getKey());
|
||||
hasClient();
|
||||
}
|
||||
} catch (java.lang.Exception e) {
|
||||
wsClients.remove(entry.getKey());
|
||||
hasClient();
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
// http
|
||||
for (Map.Entry<String, ChannelHandlerContext> entry : httpClients.entrySet()) {
|
||||
try {
|
||||
if (entry.getValue().channel().isWritable()) {
|
||||
entry.getValue().writeAndFlush(Unpooled.copiedBuffer(data));
|
||||
} else {
|
||||
httpClients.remove(entry.getKey());
|
||||
hasClient();
|
||||
}
|
||||
} catch (java.lang.Exception e) {
|
||||
httpClients.remove(entry.getKey());
|
||||
hasClient();
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增客户端
|
||||
*
|
||||
* @param ctx netty client
|
||||
* @param ctype enum,ClientType
|
||||
*/
|
||||
public void addClient(ChannelHandlerContext ctx, ClientType ctype) {
|
||||
int timeout = 0;
|
||||
while (true) {
|
||||
try {
|
||||
if (header != null) {
|
||||
try {
|
||||
if (ctx.channel().isWritable()) {
|
||||
// 发送帧前先发送header
|
||||
if (ClientType.HTTP.getType() == ctype.getType()) {
|
||||
ChannelFuture future = ctx.writeAndFlush(Unpooled.copiedBuffer(header));
|
||||
future.addListener(new GenericFutureListener<Future<? super Void>>() {
|
||||
@Override
|
||||
public void operationComplete(Future<? super Void> future) throws FrameGrabber.Exception {
|
||||
if (future.isSuccess()) {
|
||||
httpClients.put(ctx.channel().id().toString(), ctx);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (ClientType.WEBSOCKET.getType() == ctype.getType()) {
|
||||
ChannelFuture future = ctx
|
||||
.writeAndFlush(new BinaryWebSocketFrame(Unpooled.copiedBuffer(header)));
|
||||
future.addListener(new GenericFutureListener<Future<? super Void>>() {
|
||||
@Override
|
||||
public void operationComplete(Future<? super Void> future) throws FrameGrabber.Exception {
|
||||
if (future.isSuccess()) {
|
||||
wsClients.put(ctx.channel().id().toString(), ctx);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} catch (java.lang.Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// 等待推拉流启动
|
||||
Thread.sleep(50);
|
||||
// 启动录制器失败
|
||||
timeout += 50;
|
||||
if (timeout > 30000) {
|
||||
break;
|
||||
}
|
||||
} catch (java.lang.Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,576 @@
|
||||
package com.ruoyi.video.thread;
|
||||
|
||||
import com.arcsoft.face.toolkit.ImageInfo;
|
||||
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;
|
||||
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.GenericFutureListener;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bytedeco.ffmpeg.avcodec.AVPacket;
|
||||
import org.bytedeco.ffmpeg.global.avcodec;
|
||||
import org.bytedeco.ffmpeg.global.avutil;
|
||||
import org.bytedeco.javacv.FFmpegFrameGrabber;
|
||||
import org.bytedeco.javacv.FFmpegFrameRecorder;
|
||||
import org.bytedeco.javacv.FFmpegLogCallback;
|
||||
import org.bytedeco.javacv.Frame;
|
||||
import org.bytedeco.javacv.FrameGrabber;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
|
||||
/**
|
||||
* @Author: orange
|
||||
* @CreateTime: 2025-01-16
|
||||
*/
|
||||
@Slf4j
|
||||
public class MediaTransferFlvByJavacv extends MediaTransfer implements Runnable {
|
||||
|
||||
static {
|
||||
avutil.av_log_set_level(avutil.AV_LOG_ERROR);
|
||||
FFmpegLogCallback.set();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ws客户端
|
||||
*/
|
||||
private ConcurrentHashMap<String, ChannelHandlerContext> wsClients = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* http客户端
|
||||
*/
|
||||
private ConcurrentHashMap<String, ChannelHandlerContext> httpClients = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 运行状态
|
||||
*/
|
||||
private volatile boolean running = false;
|
||||
|
||||
private boolean grabberStatus = false;
|
||||
|
||||
private boolean recorderStatus = false;
|
||||
|
||||
/**
|
||||
* 当前在线人数
|
||||
*/
|
||||
private int hcSize, wcSize = 0;
|
||||
|
||||
/**
|
||||
* 用于没有客户端时候的计时
|
||||
*/
|
||||
private int noClient = 0;
|
||||
|
||||
/**
|
||||
* flv header
|
||||
*/
|
||||
private byte[] header = null;
|
||||
// 输出流,视频最终会输出到此
|
||||
private ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
||||
FFmpegFrameGrabber grabber;// 拉流器
|
||||
FFmpegFrameRecorder recorder;// 推流录制器
|
||||
|
||||
/**
|
||||
* true:转复用,false:转码
|
||||
*/
|
||||
boolean transferFlag = false;// 默认转码
|
||||
|
||||
/**
|
||||
* 相机
|
||||
*/
|
||||
private CameraDto cameraDto;
|
||||
|
||||
/**
|
||||
* 监听线程,用于监听状态
|
||||
*/
|
||||
private Thread listenThread;
|
||||
|
||||
private ArcFaceEngineUtil arcFaceEngineUtil;
|
||||
|
||||
public MediaTransferFlvByJavacv(CameraDto cameraDto) {
|
||||
super();
|
||||
this.cameraDto = cameraDto;
|
||||
this.arcFaceEngineUtil = SpringUtils.getBean(ArcFaceEngineUtil.class);
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return running;
|
||||
}
|
||||
|
||||
public void setRunning(boolean running) {
|
||||
this.running = running;
|
||||
}
|
||||
|
||||
public boolean isGrabberStatus() {
|
||||
return grabberStatus;
|
||||
}
|
||||
|
||||
public void setGrabberStatus(boolean grabberStatus) {
|
||||
this.grabberStatus = grabberStatus;
|
||||
}
|
||||
|
||||
public boolean isRecorderStatus() {
|
||||
return recorderStatus;
|
||||
}
|
||||
|
||||
public void setRecorderStatus(boolean recorderStatus) {
|
||||
this.recorderStatus = recorderStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建拉流器
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected boolean createGrabber() {
|
||||
// 拉流器
|
||||
grabber = new FFmpegFrameGrabber(cameraDto.getUrl());
|
||||
// 超时时间(15秒)
|
||||
grabber.setOption("stimeout", cameraDto.getNetTimeout());
|
||||
grabber.setOption("threads", "1");
|
||||
// grabber.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
|
||||
// 设置缓存大小,提高画质、减少卡顿花屏
|
||||
grabber.setOption("buffer_size", "1024000");
|
||||
|
||||
// 读写超时,适用于所有协议的通用读写超时
|
||||
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
|
||||
grabber.setOption("rtsp_transport", "tcp");
|
||||
// 首选TCP进行RTP传输
|
||||
grabber.setOption("rtsp_flags", "prefer_tcp");
|
||||
|
||||
} else if ("rtmp".equals(cameraDto.getUrl().substring(0, 4))) {
|
||||
// rtmp拉流缓冲区,默认3000毫秒
|
||||
grabber.setOption("rtmp_buffer", "1000");
|
||||
// 默认rtmp流为直播模式,不允许seek
|
||||
// grabber.setOption("rtmp_live", "live");
|
||||
|
||||
} else if ("desktop".equals(cameraDto.getUrl())) {
|
||||
// 支持本地屏幕采集,可以用于监控屏幕、局域网和wifi投屏等
|
||||
grabber.setFormat("gdigrab");
|
||||
grabber.setOption("draw_mouse", "1");// 绘制鼠标
|
||||
grabber.setNumBuffers(0);
|
||||
grabber.setOption("fflags", "nobuffer");
|
||||
grabber.setOption("framerate", "25");
|
||||
grabber.setFrameRate(25);
|
||||
}
|
||||
|
||||
try {
|
||||
grabber.start();
|
||||
log.info("\r\n{}\r\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();
|
||||
}
|
||||
return grabberStatus = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建转码推流录制器
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected boolean createTransterOrRecodeRecorder() {
|
||||
recorder = new FFmpegFrameRecorder(bos, grabber.getImageWidth(), grabber.getImageHeight(),
|
||||
grabber.getAudioChannels());
|
||||
recorder.setFormat("flv");
|
||||
if (!transferFlag) {
|
||||
// 转码
|
||||
recorder.setInterleaved(false);
|
||||
recorder.setVideoOption("tune", "zerolatency");
|
||||
recorder.setVideoOption("preset", "ultrafast");
|
||||
recorder.setVideoOption("crf", "26");
|
||||
recorder.setVideoOption("threads", "1");
|
||||
recorder.setFrameRate(25);// 设置帧率
|
||||
recorder.setGopSize(25);// 设置gop,与帧率相同,相当于间隔1秒chan's一个关键帧
|
||||
// recorder.setVideoBitrate(500 * 1000);// 码率500kb/s
|
||||
// recorder.setVideoCodecName("libx264"); //javacv 1.5.5无法使用libx264名称,请使用下面方法
|
||||
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
|
||||
recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
|
||||
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
|
||||
// recorder.setAudioCodecName("aac");
|
||||
/**
|
||||
* 启用RDOQ算法,优化视频质量 1:在视频码率和视频质量之间取得平衡 2:最大程度优化视频质量(会降低编码速度和提高码率)
|
||||
*/
|
||||
recorder.setTrellis(1);
|
||||
recorder.setMaxDelay(0);// 设置延迟
|
||||
try {
|
||||
recorder.start();
|
||||
return recorderStatus = true;
|
||||
} catch (org.bytedeco.javacv.FrameRecorder.Exception e1) {
|
||||
log.info("启动转码录制器失败", e1);
|
||||
MediaService.cameras.remove(cameraDto.getMediaKey());
|
||||
e1.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
// 转复用
|
||||
// 不让recorder关联关闭outputStream
|
||||
recorder.setCloseOutputStream(false);
|
||||
try {
|
||||
recorder.start(grabber.getFormatContext());
|
||||
return recorderStatus = true;
|
||||
} catch (org.bytedeco.javacv.FrameRecorder.Exception e) {
|
||||
log.warn("\r\n{}\r\n启动转复用录制器失败", cameraDto.getUrl());
|
||||
// 如果转复用失败,则自动切换到转码模式
|
||||
transferFlag = false;
|
||||
if (recorder != null) {
|
||||
try {
|
||||
recorder.stop();
|
||||
} catch (org.bytedeco.javacv.FrameRecorder.Exception e1) {
|
||||
}
|
||||
}
|
||||
if (createTransterOrRecodeRecorder()) {
|
||||
log.error("\r\n{}\r\n切换到转码模式", cameraDto.getUrl());
|
||||
return true;
|
||||
}
|
||||
log.error("\r\n{}\r\n切换转码模式失败", cameraDto.getUrl());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return recorderStatus = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否支持flv的音视频编码
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private boolean supportFlvFormatCodec() {
|
||||
int vcodec = grabber.getVideoCodec();
|
||||
int acodec = grabber.getAudioCodec();
|
||||
return (cameraDto.getType() == 0)
|
||||
&& ("desktop".equals(cameraDto.getUrl()) || avcodec.AV_CODEC_ID_H264 == vcodec
|
||||
|| avcodec.AV_CODEC_ID_H263 == vcodec)
|
||||
&& (avcodec.AV_CODEC_ID_AAC == acodec || avcodec.AV_CODEC_ID_AAC_LATM == acodec);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将视频源转换为flv
|
||||
*/
|
||||
protected void transferStream2Flv() {
|
||||
if (!createGrabber()) {
|
||||
return;
|
||||
}
|
||||
transferFlag = supportFlvFormatCodec();
|
||||
if (!createTransterOrRecodeRecorder()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
grabber.flush();
|
||||
} catch (FrameGrabber.Exception e) {
|
||||
log.info("清空拉流器缓存失败", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (header == null) {
|
||||
header = bos.toByteArray();
|
||||
// System.out.println(HexUtil.encodeHexStr(header));
|
||||
bos.reset();
|
||||
}
|
||||
|
||||
running = true;
|
||||
|
||||
// 启动监听线程(用于判断是否需要自动关闭推流)
|
||||
listenClient();
|
||||
|
||||
// 时间戳计算
|
||||
long startTime = 0;
|
||||
long videoTS = 0;
|
||||
|
||||
for (; running && grabberStatus && recorderStatus; ) {
|
||||
|
||||
try {
|
||||
if (transferFlag) {
|
||||
// 转复用
|
||||
long startGrab = System.currentTimeMillis();
|
||||
AVPacket pkt = grabber.grabPacket();
|
||||
if ((System.currentTimeMillis() - startGrab) > 5000) {
|
||||
log.info("\r\n{}\r\n视频流网络异常>>>", cameraDto.getUrl());
|
||||
closeMedia();
|
||||
break;
|
||||
}
|
||||
if (null != pkt && !pkt.isNull()) {
|
||||
if (startTime == 0) {
|
||||
startTime = System.currentTimeMillis();
|
||||
}
|
||||
videoTS = 1000 * (System.currentTimeMillis() - startTime);
|
||||
// 判断时间偏移
|
||||
if (videoTS > recorder.getTimestamp()) {
|
||||
recorder.setTimestamp((videoTS));
|
||||
}
|
||||
recorder.recordPacket(pkt);
|
||||
}
|
||||
} else {
|
||||
// 转码
|
||||
long startGrab = System.currentTimeMillis();
|
||||
Frame frame = grabber.grab();
|
||||
if ((System.currentTimeMillis() - startGrab) > 5000) {
|
||||
log.info("\r\n{}\r\n视频流网络异常>>>", cameraDto.getUrl());
|
||||
closeMedia();
|
||||
break;
|
||||
}
|
||||
|
||||
if (frame != null) {
|
||||
|
||||
processFaceDetection(frame);
|
||||
|
||||
if (startTime == 0) {
|
||||
startTime = System.currentTimeMillis();
|
||||
}
|
||||
videoTS = 1000 * (System.currentTimeMillis() - startTime);
|
||||
// 判断时间偏移
|
||||
if (videoTS > recorder.getTimestamp()) {
|
||||
// System.out.println("矫正时间戳: " + videoTS + " : " + recorder.getTimestamp() + "
|
||||
// -> "
|
||||
// + (videoTS - recorder.getTimestamp()));
|
||||
recorder.setTimestamp((videoTS));
|
||||
}
|
||||
recorder.record(frame);
|
||||
}
|
||||
}
|
||||
} catch (FrameGrabber.Exception e) {
|
||||
grabberStatus = false;
|
||||
MediaService.cameras.remove(cameraDto.getMediaKey());
|
||||
} catch (org.bytedeco.javacv.FrameRecorder.Exception e) {
|
||||
recorderStatus = false;
|
||||
MediaService.cameras.remove(cameraDto.getMediaKey());
|
||||
}
|
||||
|
||||
if (bos.size() > 0) {
|
||||
byte[] b = bos.toByteArray();
|
||||
bos.reset();
|
||||
|
||||
// 发送视频到前端
|
||||
sendFrameData(b);
|
||||
}
|
||||
}
|
||||
|
||||
// 启动失败,直接关闭, close包含stop和release方法。录制文件必须保证最后执行stop()方法
|
||||
try {
|
||||
recorder.close();
|
||||
grabber.close();
|
||||
bos.close();
|
||||
} catch (org.bytedeco.javacv.FrameRecorder.Exception e) {
|
||||
e.printStackTrace();
|
||||
} catch (FrameGrabber.Exception e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
closeMedia();
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送帧数据
|
||||
*
|
||||
* @param data
|
||||
*/
|
||||
private void sendFrameData(byte[] data) {
|
||||
// ws
|
||||
for (Map.Entry<String, ChannelHandlerContext> entry : wsClients.entrySet()) {
|
||||
try {
|
||||
if (entry.getValue().channel().isWritable()) {
|
||||
entry.getValue().writeAndFlush(new BinaryWebSocketFrame(Unpooled.copiedBuffer(data)));
|
||||
} else {
|
||||
wsClients.remove(entry.getKey());
|
||||
hasClient();
|
||||
}
|
||||
} catch (java.lang.Exception e) {
|
||||
wsClients.remove(entry.getKey());
|
||||
hasClient();
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
// http
|
||||
for (Map.Entry<String, ChannelHandlerContext> entry : httpClients.entrySet()) {
|
||||
try {
|
||||
if (entry.getValue().channel().isWritable()) {
|
||||
entry.getValue().writeAndFlush(Unpooled.copiedBuffer(data));
|
||||
} else {
|
||||
httpClients.remove(entry.getKey());
|
||||
hasClient();
|
||||
}
|
||||
} catch (java.lang.Exception e) {
|
||||
httpClients.remove(entry.getKey());
|
||||
hasClient();
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断有没有客户端,关闭流
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public void hasClient() {
|
||||
|
||||
int newHcSize = httpClients.size();
|
||||
int newWcSize = wsClients.size();
|
||||
if (hcSize != newHcSize || wcSize != newWcSize) {
|
||||
hcSize = newHcSize;
|
||||
wcSize = newWcSize;
|
||||
log.info("\r\n{}\r\nhttp连接数:{}, ws连接数:{} \r\n", cameraDto.getUrl(), newHcSize, newWcSize);
|
||||
}
|
||||
|
||||
// 无需自动关闭
|
||||
if (!cameraDto.isAutoClose()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (httpClients.isEmpty() && wsClients.isEmpty()) {
|
||||
// 等待20秒还没有客户端,则关闭推流
|
||||
if (noClient > cameraDto.getNoClientsDuration()) {
|
||||
closeMedia();
|
||||
} else {
|
||||
noClient += 1000;
|
||||
// log.info("\r\n{}\r\n {} 秒自动关闭推拉流 \r\n", camera.getUrl(), noClientsDuration-noClient);
|
||||
}
|
||||
} else {
|
||||
// 重置计时
|
||||
noClient = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听客户端,用于判断无人观看时自动关闭推流
|
||||
*/
|
||||
public void listenClient() {
|
||||
listenThread = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
while (running) {
|
||||
hasClient();
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
listenThread.start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 关闭流媒体
|
||||
*/
|
||||
private void closeMedia() {
|
||||
running = false;
|
||||
MediaService.cameras.remove(cameraDto.getMediaKey());
|
||||
|
||||
// 媒体异常时,主动断开前端长连接
|
||||
for (Map.Entry<String, ChannelHandlerContext> entry : wsClients.entrySet()) {
|
||||
try {
|
||||
entry.getValue().close();
|
||||
} catch (java.lang.Exception e) {
|
||||
} finally {
|
||||
wsClients.remove(entry.getKey());
|
||||
}
|
||||
}
|
||||
for (Map.Entry<String, ChannelHandlerContext> entry : httpClients.entrySet()) {
|
||||
try {
|
||||
entry.getValue().close();
|
||||
} catch (java.lang.Exception e) {
|
||||
} finally {
|
||||
httpClients.remove(entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增客户端
|
||||
*
|
||||
* @param ctx netty client
|
||||
* @param ctype enum,ClientType
|
||||
*/
|
||||
public void addClient(ChannelHandlerContext ctx, ClientType ctype) {
|
||||
int timeout = 0;
|
||||
while (true) {
|
||||
try {
|
||||
if (header != null) {
|
||||
try {
|
||||
if (ctx.channel().isWritable()) {
|
||||
// 发送帧前先发送header
|
||||
if (ClientType.HTTP.getType() == ctype.getType()) {
|
||||
ChannelFuture future = ctx.writeAndFlush(Unpooled.copiedBuffer(header));
|
||||
future.addListener(new GenericFutureListener<Future<? super Void>>() {
|
||||
@Override
|
||||
public void operationComplete(Future<? super Void> future) throws FrameGrabber.Exception {
|
||||
if (future.isSuccess()) {
|
||||
httpClients.put(ctx.channel().id().toString(), ctx);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (ClientType.WEBSOCKET.getType() == ctype.getType()) {
|
||||
ChannelFuture future = ctx
|
||||
.writeAndFlush(new BinaryWebSocketFrame(Unpooled.copiedBuffer(header)));
|
||||
future.addListener(new GenericFutureListener<Future<? super Void>>() {
|
||||
@Override
|
||||
public void operationComplete(Future<? super Void> future) throws FrameGrabber.Exception {
|
||||
if (future.isSuccess()) {
|
||||
wsClients.put(ctx.channel().id().toString(), ctx);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} catch (java.lang.Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// 等待推拉流启动
|
||||
Thread.sleep(50);
|
||||
// 启动录制器失败
|
||||
timeout += 50;
|
||||
if (timeout > 30000) {
|
||||
break;
|
||||
}
|
||||
} catch (java.lang.Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
transferStream2Flv();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
package com.ruoyi.video.thread;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.ruoyi.video.common.MediaConstant;
|
||||
import com.ruoyi.video.domain.dto.CameraDto;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* hls切片
|
||||
*/
|
||||
@Slf4j
|
||||
public class MediaTransferHls extends MediaTransfer {
|
||||
|
||||
/**
|
||||
* 运行状态
|
||||
*/
|
||||
private boolean running = false;
|
||||
|
||||
private boolean enableLog = false;
|
||||
private Process process;
|
||||
private Thread inputThread;
|
||||
private Thread errThread;
|
||||
|
||||
private int port = 8888;
|
||||
|
||||
/**
|
||||
* 相机
|
||||
*/
|
||||
private CameraDto cameraDto;
|
||||
|
||||
/**
|
||||
* cmd
|
||||
*/
|
||||
private List<String> command = new ArrayList<>();
|
||||
|
||||
public boolean isRunning() {
|
||||
return running;
|
||||
}
|
||||
|
||||
public void setRunning(boolean running) {
|
||||
this.running = running;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param cameraDto
|
||||
*/
|
||||
public MediaTransferHls(CameraDto cameraDto, int port) {
|
||||
this.cameraDto = cameraDto;
|
||||
this.port = port;
|
||||
buildCommand();
|
||||
}
|
||||
|
||||
/**
|
||||
* String cmd = "ffmpeg -i rtsp://admin:VZCDOY@192.168.2.120:554/Streaming/Channels/102 -r 25 -g 25 -c:v libx264 -c:a aac -f hls -hls_list_size 1 -hls_wrap 6 -hls_time 1 -hls_base_url /ts/"+22+"/ -method put http://localhost:8888/record/"+22+"/out.m3u8";
|
||||
*/
|
||||
private void buildCommand() {
|
||||
|
||||
command.add(System.getProperty(MediaConstant.ffmpegPathKey));
|
||||
command.add("-i");
|
||||
command.add(cameraDto.getUrl());
|
||||
command.add("-r");
|
||||
command.add("25");
|
||||
command.add("-g");
|
||||
command.add("25");
|
||||
command.add("-c:v");
|
||||
command.add("h264"); //javacv 1.5.5 无法使用libx264
|
||||
command.add("-c:a");
|
||||
command.add("aac");
|
||||
command.add("-f");
|
||||
command.add("hls");
|
||||
command.add("-hls_list_size");
|
||||
command.add("1");
|
||||
command.add("-hls_wrap");
|
||||
command.add("6");
|
||||
command.add("-hls_time");
|
||||
command.add("1");
|
||||
command.add("-hls_base_url");
|
||||
command.add("/ts/"+cameraDto.getMediaKey()+"/");
|
||||
command.add("-method");
|
||||
command.add("put");
|
||||
command.add("http://localhost:"+port+"/record/"+cameraDto.getMediaKey()+"/out.m3u8");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行
|
||||
*/
|
||||
public void execute() {
|
||||
String join = CollUtil.join(command, " ");
|
||||
log.info(join);
|
||||
|
||||
try {
|
||||
process = new ProcessBuilder(command).start();
|
||||
running = true;
|
||||
dealStream(process);
|
||||
} catch (IOException e) {
|
||||
running = false;
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭
|
||||
*/
|
||||
public void stop() {
|
||||
this.running = false;
|
||||
try {
|
||||
process.destroy();
|
||||
log.info("关闭媒体流-ffmpeg,{} ", cameraDto.getUrl());
|
||||
} catch (Exception e) {
|
||||
process.destroyForcibly();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 控制台输出
|
||||
*
|
||||
* @param process
|
||||
*/
|
||||
private void dealStream(Process process) {
|
||||
if (process == null) {
|
||||
return;
|
||||
}
|
||||
// 处理InputStream的线程
|
||||
inputThread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
String line = null;
|
||||
try {
|
||||
while (running) {
|
||||
line = in.readLine();
|
||||
if (line == null) {
|
||||
break;
|
||||
}
|
||||
if (enableLog) {
|
||||
log.info("output: " + line);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
running = false;
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// 处理ErrorStream的线程
|
||||
errThread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream()));
|
||||
String line = null;
|
||||
try {
|
||||
while (running) {
|
||||
line = err.readLine();
|
||||
if (line == null) {
|
||||
break;
|
||||
}
|
||||
if (enableLog) {
|
||||
log.info("err: " + line);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
running = false;
|
||||
err.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
inputThread.start();
|
||||
errThread.start();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user