package com.robot.cluster.rtsp.push;
import com.robot.cluster.rtsp.javacv.FFmpegFrameGrabber;
import com.robot.cluster.rtsp.pojo.Config;
import com.robot.cluster.rtsp.pojo.RosCameraSessionHolder;
import org.bytedeco.ffmpeg.avcodec.AVPacket;
import org.bytedeco.ffmpeg.avformat.AVFormatContext;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.FFmpegLogCallback;
import org.bytedeco.javacv.FrameGrabber;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
import static org.bytedeco.ffmpeg.global.avcodec.av_packet_unref;
public class CameraWorker extends Thread {
private final static Logger logger = LoggerFactory.getLogger(CameraWorker.class);
// 配置类
private Config config;
private RosCameraSessionHolder pojo;// 设备信息
private FFmpegFrameRecorder recorder;// 解码器
private FFmpegFrameGrabber grabber;// 采集器
private int err_index = 0;// 推流过程中出现错误的次数
private int exitcode = 0;// 退出状态码:0-正常退出;1-手动中断;
private double framerate = 0;// 帧率
private int noFrameIndex = 0;
private volatile boolean running = false;
private int grabberCounter = 0;
private long grabberStartTs = 0;
private long grabberEmptyNowTs = 0;
private long lastUpdateTs = System.currentTimeMillis();
public int getGrabberCounter() {
return grabberCounter;
}
public RosCameraSessionHolder getPojo() {
return pojo;
}
public CameraWorker(RosCameraSessionHolder pojo, Config config) {
this.pojo = pojo;
this.config = config;
}
public boolean isRunning() {
return running;
}
public int getNoFrameIndex() {
return noFrameIndex;
}
@Override
public void run() {
if (!this.running) {
this.running = true;
startWork();
} else {
logger.info("[已经在运行中]-[serverId:{}]-[ip:{}]-[rtspUrl:{}]-[liveUrl:{}]", pojo.getServerId(), pojo.getCameraIp(), pojo.getRtspUrl(), pojo.getRtmpUrl());
}
}
/**
* @return void
* @Title: release
* @Description:资源释放
**/
public void release() {
try {
if (recorder != null) {
//recorder.stop();
recorder.close();
recorder = null;
}
if (grabber != null) {
grabber.close();
logger.info("[grapper stop]机器人:{},节点:{}", pojo.getRobotName(), pojo.getRobotNodeName());
grabber = null;
}
this.running = false;
} catch (Exception e) {
logger.error("[释放资源错误]", e);
}
}
private Exception streamEx;
/**
* @return void
* @Title: push
* @Description:推送视频流数据包
**/
public void startWork() {
try {
FFmpegLogCallback.set();
avutil.av_log_set_level(avutil.AV_LOG_INFO);
grabberStartTs = System.currentTimeMillis();
grabber = new FFmpegFrameGrabber(pojo.getRtspUrl());
grabber.setOption("rtsp_transport", "tcp");
grabber.setOption("rw_timeout", "5000000");
grabber.setSampleMode(FrameGrabber.SampleMode.FLOAT);
grabber.setMaxDelay(20000);
grabber.setOption("stimeout", "5000000");
String mainCode;
if ("sub".equals(pojo.getStream())) {
grabber.setImageWidth(704);
grabber.setImageHeight(576);
mainCode = config.getSub_code();
} else if ("main".equals(pojo.getStream())) {
grabber.setImageWidth(704);
grabber.setImageHeight(576);
mainCode = config.getMain_code();
} else {
grabber.setImageWidth(704);
grabber.setImageHeight(576);
mainCode = config.getMain_code();
}
logger.info("[开始]-[启动抓流:{},rtspUrl:{},rtmpUrl:{},main_code:{}]", pojo.getRobotName(), pojo.getRtspUrl(), pojo.getRtmpUrl(), config.getMain_code());
grabber.start(mainCode);
grabberStartTs = System.currentTimeMillis();
logger.info("[结束]-[启动抓流:{},rtspUrl:{},rtmpUrl:{},main_code:{}]", pojo.getRobotName(), pojo.getRtspUrl(), pojo.getRtmpUrl(), config.getMain_code());
// 部分监控设备流信息里携带的帧率为9000,如出现此问题,会导致dts、pts时间戳计算失败,播放器无法播放,故出现错误的帧率时,默认为25帧
if (grabber.getFrameRate() > 0 && grabber.getFrameRate() < 100) {
framerate = grabber.getFrameRate();
} else {
framerate = 25.0;
}
int width = grabber.getImageWidth();
int height = grabber.getImageHeight();
// 若视频像素值为0,说明拉流异常,程序结束
if (width == 0 && height == 0) {
logger.error(pojo.getRtspUrl() + " 拉流异常!");
grabber.stop();
grabber.close();
release();
return;
}
recorder = new FFmpegFrameRecorder(pojo.getRtmpUrl(), grabber.getImageWidth(), grabber.getImageHeight());
recorder.setInterleaved(true);
// 关键帧间隔,一般与帧率相同或者是视频帧率的两倍
recorder.setGopSize((int) framerate * 2);
// 视频帧率(保证视频质量的情况下最低25,低于25会出现闪屏)
recorder.setFrameRate(framerate);
// 设置比特率
recorder.setVideoBitrate(grabber.getVideoBitrate());
// 封装flv格式
recorder.setFormat("flv");
// h264编/解码器
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
Map<String, String> videoOption = new HashMap<>();
// 该参数用于降低延迟
videoOption.put("tune", "zerolatency");
/**
** 权衡quality(视频质量)和encode speed(编码速度) values(值): *
* ultrafast(终极快),superfast(超级快), veryfast(非常快), faster(很快), fast(快), *
* medium(中等), slow(慢), slower(很慢), veryslow(非常慢) *
* ultrafast(终极快)提供最少的压缩(低编码器CPU)和最大的视频流大小;而veryslow(非常慢)提供最佳的压缩(高编码器CPU)的同时降低视频流的大小
*/
videoOption.put("preset", "ultrafast");
// 画面质量参数,0~51;18~28是一个合理范围
videoOption.put("crf", "28");
recorder.setOptions(videoOption);
AVFormatContext fc = grabber.getFormatContext();
recorder.start(fc);
logger.info("[开始推流]-[机器人:{}]-[摄像头:{}]-[rtspUrl:{}]-[liveUrl:{}]", pojo.getRobotName(), pojo.getRobotNodeName(), pojo.getRtspUrl(), pojo.getRtmpUrl());
// 清空探测时留下的缓存
grabber.flush();
AVPacket pkt = null;
long dts = 0;
long pts = 0;
int timebase = 0;
while (running && isNotExpireGrabber() && grabber != null && recorder != null) {
// if (exitcode == 1)
// break;
// if (noFrameIndex > 500) {
// throw new RuntimeException(String.format("[机器人:%s]-[节点:%s]-[rtspUrl:%s]-[rtmpUrl:%s]空包次数大于50次,退出程序", pojo.getRobotName(), pojo.getRobotNodeName(), pojo.getRtspUrl(), pojo.getRtmpUrl()));
// }
pkt = grabber.grabPacket();
if (pkt == null || pkt.size() == 0 || pkt.data() == null) {
// 空包记录次数跳过
//logger.info("[空包:{}]-[serverId:{}]-[ip:{}]-[channel:{}]-[stream:{}]-[rtspUrl:{}]-[liveUrl:{}]", noFrameIndex, pojo.getServerId(), pojo.getIp(), pojo.getChannel(), pojo.getStream(), pojo.getRtsp(), pojo.getRtmp());
noFrameIndex++;
err_index++;
grabberEmptyNowTs = System.currentTimeMillis();
continue;
}
// 过滤音频
if (pkt.stream_index() == 1) {
av_packet_unref(pkt);
continue;
}
// if (pkt.dts() == avutil.AV_NOPTS_VALUE && pkt.pts() == avutil.AV_NOPTS_VALUE || pkt.pts() < dts) {
// logger.info("异常pkt 当前pts: " + pkt.pts() + " dts: " + pkt.dts() + " 上一包的pts: " + pts + " dts: "
// + dts);
// err_index++;
// av_packet_unref(pkt);
// continue;
// }
noFrameIndex = 0;
grabberEmptyNowTs = 0;
grabberCounter++;
lastUpdateTs = System.currentTimeMillis();
// 矫正sdk回调数据的dts,pts每次不从0开始累加所导致的播放器无法续播问题
pkt.pts(pts);
pkt.dts(dts);
recorder.setTimestamp(grabber.getTimestamp());
err_index += (recorder.recordPacket(pkt) ? 0 : 1);
// pts,dts累加
timebase = grabber.getFormatContext().streams(pkt.stream_index()).time_base().den();
pts += timebase / (int) framerate;
dts += timebase / (int) framerate;
// 将缓存空间的引用计数-1,并将Packet中的其他字段设为初始值。如果引用计数为0,自动的释放缓存空间。
av_packet_unref(pkt);
}
} catch (Exception e) {
streamEx = e;
logger.info((String.format("[推流异常]-[机器人:%s]-[摄像头:%s]-[rtspUrl:%s]-[liveUrl:%s]", pojo.getRobotName(), pojo.getRobotNodeName(), pojo.getRtspUrl(), pojo.getRtmpUrl())));
logger.error(String.format("[推流异常]-[机器人:%s]-[摄像头:%s]-[rtspUrl:%s]-[liveUrl:%s]", pojo.getRobotName(), pojo.getRobotNodeName(), pojo.getRtspUrl(), pojo.getRtmpUrl()), e);
} finally {
release();
if (streamEx != null) {
pojo.setStream("STREAM_ERROR");
pojo.setDesc("推流异常");
} else {
pojo.setStatus("STREAM_END");
pojo.setDesc("推流结束");
}
//CameraManager.get().removeCamera(pojo.getServerId());
CameraManager.get().updateSession(pojo);
logger.info("[推流结束]-[机器人:{}]-[摄像头:{}]-[channel:{}]-[stream:{}]-[rtspUrl:{}]-[liveUrl:{}]", pojo.getRobotName(), pojo.getRobotNodeName(), pojo.getChannel(), pojo.getStream(), pojo.getRtspUrl(), pojo.getRtmpUrl());
}
}
private boolean isNotExpireGrabber() {
if (grabberEmptyNowTs == 0)
return true;
if (grabberEmptyNowTs - grabberStartTs > 120000) {
logger.info("[拉流超时]-[机器人:{}]-[摄像头:{}][rtspUrl:{}]-[liveUrl:{}]-[expire:{}]", pojo.getRobotName(), pojo.getRobotNodeName(), pojo.getRtspUrl(), pojo.getRtmpUrl(), grabberEmptyNowTs);
return false;
}
return true;
}
public long getLastUpdateTs() {
return lastUpdateTs;
}
public int getErrIndex() {
return err_index;
}
public void setRunning(boolean running) {
this.running = running;
}
@Override
public void interrupt() {
if (!this.isInterrupted()) {
super.interrupt();
}
}
public void stopWork() {
if (this.running) {
this.running = false;
}
}
}
javaCv实现摄像头拉流,没有解码开销,稳定高效
最新推荐文章于 2024-07-15 16:07:45 发布