javacv icon indicating copy to clipboard operation
javacv copied to clipboard

pull after push will show ‘no image’

Open shuaisong opened this issue 1 year ago • 7 comments

package com.zjxf.hsdji.utils;

import com.frank.live.FFmpegUtil; import com.zjxf.hsdji.MApplication;

import org.bytedeco.ffmpeg.avutil.AVFrame; import org.bytedeco.javacv.FFmpegFrameGrabber; import org.bytedeco.javacv.FFmpegFrameRecorder; import org.bytedeco.javacv.FFmpegLogCallback; import org.bytedeco.javacv.Frame; import org.bytedeco.javacv.FrameRecorder; import org.bytedeco.javacv.OpenCVFrameConverter; import org.bytedeco.opencv.opencv_videoio.VideoWriter; import org.bytedeco.opencv.opencv_core.Size;

import java.io.File; import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedOutputStream;

import static org.bytedeco.ffmpeg.global.avcodec.AV_CODEC_ID_H264; import static org.bytedeco.ffmpeg.global.avcodec.AV_CODEC_ID_MPEG1VIDEO; import static org.bytedeco.ffmpeg.global.avutil.AV_LOG_PRINT_LEVEL; import static org.bytedeco.ffmpeg.global.avutil.AV_PIX_FMT_BGR24; import static org.bytedeco.ffmpeg.global.avutil.AV_PIX_FMT_RGB48; import static org.bytedeco.ffmpeg.global.avutil.AV_PIX_FMT_YUV420P; import static org.bytedeco.ffmpeg.global.avutil.AV_PIX_FMT_YUV420P10; import static org.bytedeco.ffmpeg.global.avutil.av_log_set_level;

/**

  • 推流到rtsp、rtmp服务器 */ public class Pusher extends Thread {

    private final PipedInputStream pis = new PipedInputStream(); private final PipedOutputStream pos = new PipedOutputStream(); private final MyLog log; private volatile boolean running = true; private boolean flag = false;

    public Pusher() { log = new MyLog(); try { // 使用管道流实现线程间通信 pos.connect(pis); av_log_set_level(AV_LOG_PRINT_LEVEL); FFmpegLogCallback.set(); } catch (IOException e) { log.info("pos connect pis error.{}" + e.getMessage()); } }

    private int num = 0;

    /**

    • 将视频流写入管道 */ public void onMediaStream(byte[] data) { try { if (!flag) { log.info("receive data..."); flag = true; } pos.flush(); pos.write(data); log.info("write"); } catch (Exception e) { log.error("write video data error.{}" + e.getMessage()); running = false; try { pos.close(); } catch (IOException ex) { // log.error("pos close error.{}" + ex.getMessage()); } } }

    /**

    • 转流器, 指定format */ public void recordPushWithFormat() { long startTime = 0; long videoTS;

      try { log.info("grabber start ... {} > {}"); // 从管道流中读取视频流 // maximumSize 设置为0,不设置会阻塞 FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(pis, 0);

       //此配置以减少启动时间!若不设置,启动最起码半分钟;
       //类似一个缓冲区,用来检测流的正确性,值越小,启动越快
      

// grabber.setOption("probesize", "30"); // grabber.setOption("buffer_size", "10"); // grabber.setOption("threads", "1"); grabber.setOption("g", "25"); grabber.setVideoOption("vcodec", "copy"); // grabber.setOption("flags", "ignorecropped"); //// grabber.setOption("flags", "low_delay"); // grabber.setOption("discardcorrupted", "1"); // grabber.setOption("skip_loop_filter", "1"); // grabber.setVideoOption("vcodec", "h264"); //// //硬解码 //// grabber.setVideoOption("hwaccel", "mediacodec"); //// grabber.setVideoOption("allow_hwaccel", "1"); //// grabber.setVideoOption("hwaccel_device", "auto"); ////// //// grabber.setOption("preset", "slow"); // 使用slow编码预设 //// grabber.setOption("tune", "film"); // 使用film的tune //// grabber.setOption("crf", "36"); // 设置视频质量 //// // grabber.setFrameRate(30); // grabber.setVideoBitrate(5 * 1024 * 1024); grabber.setImageWidth(1920); grabber.setImageHeight(1080); // grabber.setPixelFormat(AV_PIX_FMT_BGR24); // grabber.setVideoCodec(AV_CODEC_ID_H264); // grabber.setAudioCodec(avcodec.AV_CODEC_ID_NONE); grabber.setFormat("h264"); // grabber.setAudioChannels(0); //阻塞式,直到通道有数据 grabber.start(); int pixelFormat = grabber.getPixelFormat(); int videoBitrate = grabber.getVideoBitrate(); log.info("grabber start suc. and start recorder ...pixelFormat: " + pixelFormat); log.info("grabber start suc. and start recorder ...videoBitrate: " + videoBitrate); log.info("grabber start suc. and start recorder ...ImageHeight: " + grabber.getImageHeight());

        double frameRate = grabber.getFrameRate();
        log.info("grabber start suc. and start recorder ...frameRate: " + frameRate);
        // 获取推流地址对应的recorder,用于录制视频
        // rtmp对应format是flv
        // rtsp对应format是rtsp
        FrameRecorder rtmpRecorder = getRecorder(frameRate);
        rtmpRecorder.start();
        log.info("VideoCodecName:" + rtmpRecorder.getVideoCodecName());
        Frame grabframe;
        // 创建 VideoWriter 对象,用于将帧写入视频文件
        Size size = new Size(1920, 1080);
        int fourcc = VideoWriter.fourcc((byte) 'M', (byte) 'J', (byte) 'P', (byte) 'G');
        OpenCVFrameConverter.ToMat converter = new OpenCVFrameConverter.ToMat();
        File file = new File(MApplication.getInstance().getExternalCacheDir(), "output.avi");
        VideoWriter writer = new VideoWriter(file.getAbsolutePath(), fourcc, 30.0, size, true);

        // 假设一秒钟15帧,那么两帧间隔就是(1000/15)毫秒
        int interVal = (int) (1000 / frameRate);

// 发送完一帧后sleep的时间,不能完全等于(1000/frameRate),不然会卡顿, // 要更小一些,这里取八分之一 interVal /= 10;

        boolean isKey = true;
        int frameNum = 0;
        // 从视频流中捕获帧以录制视频
        while (running) {
            log.info("get frame");
            grabframe = grabber.grabImage();
            if (grabframe == null) {
                return;
            }
            int imageWidth = grabframe.imageWidth;
            int imageHeight = grabframe.imageHeight;
            log.info("get imageWidth" + imageWidth + "::imageHeight" + imageHeight);

            log.info("grabframe.image" + (grabframe.image == null) + "frame.samples " + (grabframe.samples == null));
            Object opaque = grabframe.opaque;
            if (opaque instanceof AVFrame) {
                AVFrame avFrame = (AVFrame) opaque;
                log.info("AVFrame format:" + avFrame.format());
            }
            if (startTime == 0) {
                startTime = System.currentTimeMillis();
            }
            try {

// Mat mat = converter.convert(grabframe); // writer.write(mat); } catch (Exception e) { log.error("writer error." + e.getMessage()); }

            videoTS = 1000 * (System.currentTimeMillis() - startTime);

            // 推流到rtmp server
            if (videoTS > rtmpRecorder.getTimestamp()) {
                rtmpRecorder.setTimestamp(videoTS);
            }
            log.info("grabframe is keyFrame " + grabframe.keyFrame);
            rtmpRecorder.record(grabframe);
            if (frameNum >= frameRate) {
                frameNum = 0;
            } else
                frameNum++;
            log.error("is keyFrame " + grabframe.keyFrame);

// Thread.sleep(interVal); } } catch (Exception e) { log.error("record push error." + e.getMessage()); } finally { try { pis.close(); } catch (IOException e) { log.error("pis close error.{}" + e.getMessage()); } }

}

private FrameRecorder getRecorder(double frameRate) {

    FrameRecorder recorder = new FFmpegFrameRecorder("rtmp://192.168.100.115:1935/live/video1", 1920, 1080, 0);

// recorder.setInterleaved(true); recorder.setVideoOption("tune", "zerolatency"); recorder.setVideoOption("preset", "ultrafast"); // recorder.setVideoOption("crf", "28"); // recorder.setImageHeight(1088); // recorder.setImageWidth(1920); recorder.setVideoBitrate(5 * 1024 * 1024);//码率 越大越清晰 recorder.setVideoCodec(AV_CODEC_ID_H264); // recorder.setPixelFormat(AV_PIX_FMT_YUV420P); // recorder.setMaxBFrames(0); recorder.setVideoQuality(30); recorder.setFormat("flv");//flv rtsp recorder.setFrameRate(30);//帧率 30fps 每秒包含的帧数 24-30越大越流畅 recorder.setGopSize((int) (302));//302 每60帧存在一个关键帧 // recorder.setVideoCodecName("libx264"); // recorder.setVideoOption("x264opts", "keyint=25:min-keyint=25"); // recorder.setVideoOption("x264-params", "keyint=25"); recorder.setOption("g", "10"); recorder.setAudioChannels(0); // recorder.setMaxBFrames(10);

// recorder.setOption("probesize", "1000000"); // recorder.setOption("buffer_size", "5000000"); // recorder.setSampleRate(44100); // recorder.setTrellis(0);

    return recorder;
}

public void stopPush() {
    this.running = false;
    this.interrupt();
}

public boolean isRunning() {
    return this.running;
}

@Override
public void run() {
    recordPushWithFormat();
}

}

shuaisong avatar Jun 20 '23 10:06 shuaisong

implementation group:'org.bytedeco',name:'javacv',version:'1.5.9'

implementation group:'org.bytedeco',name:'ffmpeg',version:'6.0-1.5.9'
implementation (group:'org.bytedeco',name:'ffmpeg',version:'6.0-1.5.9',classifier:'android-arm')
implementation (group:'org.bytedeco',name:'ffmpeg',version:'6.0-1.5.9',classifier:'android-arm64')

implementation group:'org.bytedeco',name:'opencv',version:'4.7.0-1.5.9'
implementation (group:'org.bytedeco',name:'opencv',version:'4.7.0-1.5.9',classifier:'android-arm')
implementation (group:'org.bytedeco',name:'opencv',version:'4.7.0-1.5.9',classifier:'android-arm64')

implementation group:'org.bytedeco',name:'openblas',version:'0.3.23-1.5.9'
implementation (group:'org.bytedeco',name:'openblas',version:'0.3.23-1.5.9',classifier:'android-arm')
implementation (group:'org.bytedeco',name:'openblas',version:'0.3.23-1.5.9',classifier:'android-arm64')

implementation group:'org.bytedeco',name:'javacpp',version:'1.5.9'
implementation (group:'org.bytedeco',name:'javacpp',version:'1.5.9',classifier:'android-arm')
implementation (group:'org.bytedeco',name:'javacpp',version:'1.5.9',classifier:'android-arm64')

shuaisong avatar Jun 20 '23 10:06 shuaisong

Some filters might buffer frames, that's normal.

saudet avatar Jun 21 '23 04:06 saudet

how fix it?

shuaisong avatar Jun 21 '23 08:06 shuaisong

grabber.setFormat("h264");

I don't think that's a format supported by FFmpeg. Check the log.

Please make sure that FFmpegLogCallback.set() has been called.

saudet avatar Jun 21 '23 08:06 saudet

I set it,and push success ,but no image

shuaisong avatar Jun 21 '23 09:06 shuaisong

2023-06-21 13:59:40.094 W [MediaServer] [12119-event poller 11] MediaSink.cpp:55 operator() | Cached frame of unready track(H264) is too much, now cleared 2023-06-21 13:59:44.806 W [MediaServer] [12119-event poller 11] MediaSink.cpp:55 operator() | Cached frame of unready track(H264) is too much, now cleared 2023-06-21 13:59:45.290 D [MediaServer] [12119-event poller 11] MediaSink.cpp:150 emitAllTrackReady | All track ready use 10044ms 2023-06-21 13:59:45.290 W [MediaServer] [12119-event poller 11] MediaSink.cpp:157 emitAllTrackReady | Track not ready for a long time, ignored: H264 2023-06-21 14:00:07.987 I [MediaServer] [12119-event poller 11] RtmpProtocol.cpp:444 check_C1_Digest | check rtmp complex handshark success! 2023-06-21 14:00:08.032 D [MediaServer] [12119-event poller 11] RtmpSession.cpp:367 operator() | 7180-157(192.168.4.1:49363) play 回复时间:0ms 2023-06-21 14:00:18.981 D [MediaServer] [12119-event poller 11] RtmpSession.cpp:130 operator() | 7181-164(192.168.4.1:20609) publish 回复时间:1ms 2023-06-21 14:00:19.256 I [MediaServer] [12119-event poller 11] MediaSource.cpp:523 emitEvent | 媒体注册:rtmp://defaultVhost/live/kunpeng01

shuaisong avatar Jun 21 '23 09:06 shuaisong

That doesn't look like FFmpeg's log. You'll need to check its log for any errors and warnings.

saudet avatar Jun 21 '23 11:06 saudet