JavaCV FrameGrabber問題匯總
@Date 2018.09.27
FrameGrabber類
- JavaCV中FrameGrabber類可以連接直播流地址, 進(jìn)行解碼, 獲取Frame幀信息, 常用方式如下
FrameGrabber grabber = new FrameGrabber("rtsp:/192.168.0.0");
while(true) {
Frame frame = grabber.grabImage();
// ...
}
問題(1)
- 在如上代碼中, 若直播地址網(wǎng)絡(luò)不通, 或者連接超時, 則會出現(xiàn)程序Hang住的情況, 也就是同步阻塞卡死. 因?yàn)榭梢栽黾右粋€超時參數(shù)進(jìn)行解決.
解決辦法(1)
FrameGrabber grabber = new FrameGrabber("rtsp:/192.168.0.0");
// 增加超時參數(shù)
grabber.setOption(TimeoutOption.STIMEOUT.key(), "5000000");
while(true) {
Frame frame = grabber.grabImage();
// ...
}
問題(2)
- FrameGrabber hangs when server connection is broken
- 在問題(1)的基礎(chǔ)上, 若剛開始網(wǎng)絡(luò)是通的, 程序可以獲取到解碼后的視頻幀. 而在已經(jīng)運(yùn)行通了的時候, 網(wǎng)絡(luò)突然斷掉, 此時上面的timeout是不生效的.
- 同時可以參考GitHub上的提問(https://github.com/bytedeco/javacv/issues/1063)
解決辦法(2)
- 在GitHub上面, 作者介紹了Callback_Pointer方式, 參考(https://github.com/bytedeco/javacv/blob/master/samples/FFmpegStreamingTimeout.java)
- 但是經(jīng)過測試, 發(fā)現(xiàn)此方式還是不能解決問題(2), 在FrameGrabber外部增加callback對原類中的對象不生效.
- 故使用一種笨方式, 繼承重寫FrameGrabber類, 把超時callback增加到原代碼邏輯中.
public class CustomFrameGrabber extends FrameGrabber {
/**
* 讀流超時時間 : 10秒
*/
private static final int FRAME_READ_TIMEOUT = 10000;
// 增加callbacjk 監(jiān)聽
private final AtomicLong lastFrameTime = new AtomicLong(System.currentTimeMillis());
private final AVIOInterruptCB.Callback_Pointer cp = new avformat.AVIOInterruptCB.Callback_Pointer() {
@Override
public int call(Pointer pointer) {
// 0:continue, 1:exit
if (lastFrameTime.get() + FRAME_READ_TIMEOUT < System.currentTimeMillis()) {
MONITOR_LOGGER.warn("frame read timeout 10s.");
return 1;
}
return 0;
}
};
// ... 省略中間代碼
void startUnsafe() throws Exception {
int ret;
img_convert_ctx = null;
oc = new AVFormatContext(null);
video_c = null;
audio_c = null;
pkt = new AVPacket();
pkt2 = new AVPacket();
sizeof_pkt = pkt.sizeof();
got_frame = new int[1];
frameGrabbed = false;
frame = new Frame();
timestamp = 0;
frameNumber = 0;
pkt2.size(0);
// Open video file
AVInputFormat f = null;
if (format != null && format.length() > 0) {
if ((f = av_find_input_format(format)) == null) {
throw new Exception("av_find_input_format() error: Could not find input format \"" + format + "\".");
}
}
AVDictionary options = new AVDictionary(null);
if (frameRate > 0) {
AVRational r = av_d2q(frameRate, 1001000);
av_dict_set(options, "framerate", r.num() + "/" + r.den(), 0);
}
if (pixelFormat >= 0) {
av_dict_set(options, "pixel_format", av_get_pix_fmt_name(pixelFormat).getString(), 0);
} else if (imageMode != ImageMode.RAW) {
av_dict_set(options, "pixel_format", imageMode == ImageMode.COLOR ? "bgr24" : "gray8", 0);
}
if (imageWidth > 0 && imageHeight > 0) {
av_dict_set(options, "video_size", imageWidth + "x" + imageHeight, 0);
}
if (sampleRate > 0) {
av_dict_set(options, "sample_rate", "" + sampleRate, 0);
}
if (audioChannels > 0) {
av_dict_set(options, "channels", "" + audioChannels, 0);
}
for (Entry<String, String> e : this.options.entrySet()) {
av_dict_set(options, e.getKey(), e.getValue(), 0);
}
if (inputStream != null) {
if (readCallback == null) {
readCallback = new CustomFFmpegFrameGrabber.ReadCallback();
}
if (seekCallback == null) {
seekCallback = new CustomFFmpegFrameGrabber.SeekCallback();
}
if (!inputStream.markSupported()) {
inputStream = new BufferedInputStream(inputStream, 1024 * 1024);
}
inputStream.mark(1024 * 1024);
oc = avformat_alloc_context();
avio = avio_alloc_context(new BytePointer(av_malloc(4096)), 4096, 0, oc, readCallback, null, seekCallback);
oc.pb(avio);
filename = inputStream.toString();
inputStreams.put(oc, inputStream);
}
// 2018.09.07 add 此處一行為新增代碼, 意思是每次取到幀時, 都更新一下時間. 若遇到網(wǎng)絡(luò)斷開, hang住時, 則可用此時間判斷超時
lastFrameTime.set(System.currentTimeMillis());
oc = avformat_alloc_context();
avformat.AVIOInterruptCB cb = new avformat.AVIOInterruptCB();
cb.callback(cp);
oc.interrupt_callback(cb);
// 省略后續(xù)代碼...
}
public Frame grabFrame(boolean doAudio, boolean doVideo, boolean processImage, boolean keyFrames) throws Exception {
if (oc == null || oc.isNull()) {
throw new Exception("Could not grab: No AVFormatContext. (Has start() been called?)");
} else if ((!doVideo || video_st == null) && (!doAudio || audio_st == null)) {
return null;
}
// 2018.09.07 add 獲取幀時, 更新最新時間戳
lastFrameTime.set(System.currentTimeMillis());
// 省略后續(xù)代碼...
}
}