javaCv实现摄像头拉流,没有解码开销,稳定高效

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;
        }
    }
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值