javacv
javacv copied to clipboard
pull after push will show ‘no image’
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();
}
}
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')
Some filters might buffer frames, that's normal.
how fix it?
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.
I set it,and push success ,but no image
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
That doesn't look like FFmpeg's log. You'll need to check its log for any errors and warnings.