bytedeco / javacpp

The missing bridge between Java and native C++
Other
4.47k stars 582 forks source link

The JVM crashed while execute avformat_ find_ stream_info() #518

Closed zoooooway closed 1 month ago

zoooooway commented 2 years ago

Hello, sorry for taking up your time. I have a question to ask you. My English is not very good, so these words are translated using translation software. I need to use javacpp to open a media URL every time and take a screenshot and save the picture. Since there are multiple URLs, I use a timed task to operate each stream individually. The main code is as follows,:

public class Stream2Image {
    private static final int DEST_FORMAT = AV_PIX_FMT_YUVJ420P;

    public void safeOpenMediaAndSaveImage(String url, String outFile) throws IOException, InterruptedException {
        av_log_set_level(AV_LOG_ERROR);
        AVFormatContext pFormatCtx = null;
        AVCodecContext pCodecCtx = null;
        AVFrame pFrame = null;
        FrameData frameData = null;

        try {
            pFormatCtx = getFormatContext(url);

            if (null == pFormatCtx) {
                return;
            }

            av_dump_format(pFormatCtx, 0, url, 0);

            int videoStreamIndex = getVideoStreamIndex(pFormatCtx);
            if (videoStreamIndex < 0) {
                return;
            }

            pCodecCtx = getCodecContext(pFormatCtx, videoStreamIndex);
            if (null == pCodecCtx) {
                return;
            }

            pFrame = getSingleFrame(pCodecCtx, pFormatCtx, videoStreamIndex);
            if (null == pFrame) {
                return;
            }

            frameData = YUV420PToYUVJ420P(pCodecCtx, pFrame);
            if (null == frameData) {
                return;
            }

            saveImg(frameData.avFrame, outFile);
        } finally {
            List<Pointer> pointers = new ArrayList<>();
            if (frameData != null) {
                Pointer buffer = frameData.buffer;
                if (buffer != null) {
                    pointers.add(buffer);
                }
                Pointer avFrame = frameData.avFrame;
                if (avFrame != null) {
                    pointers.add(avFrame);
                }
            }
            pointers.add(pFrame);
            release(true, null, null, pCodecCtx, pFormatCtx, pointers);
        }
    }

    private AVFormatContext getFormatContext(String url) {
        AVFormatContext pFormatCtx = new AVFormatContext(null);
        AVDictionary options = new AVDictionary(pFormatCtx);
        av_dict_set(options, "rtsp_transport", "tcp", 0);
        if (avformat_open_input(pFormatCtx, url, null, options) != 0) {
            return null;
        }

        if (avformat_find_stream_info(pFormatCtx, (PointerPointer<Pointer>) null) < 0) {
            return null;
        }
        return pFormatCtx;
    }

    private static int getVideoStreamIndex(AVFormatContext pFormatCtx) {
        int videoStream = -1;
        for (int i = 0; i < pFormatCtx.nb_streams(); i++) {
            if (pFormatCtx.streams(i).codec().codec_type() == AVMEDIA_TYPE_VIDEO) {
                videoStream = i;
                break;
            }
        }
        return videoStream;
    }

    private AVCodecContext getCodecContext(AVFormatContext pFormatCtx, int videoStreamIndex) {
        AVCodec pCodec;

        AVCodecContext pCodecCtx = pFormatCtx.streams(videoStreamIndex).codec();

        pCodec = avcodec_find_decoder(pCodecCtx.codec_id());

        if (pCodec == null) {
            return null;
        }

        if (avcodec_open2(pCodecCtx, pCodec, (AVDictionary) null) < 0) {
            return null;
        }

        return pCodecCtx;
    }

    private AVFrame getSingleFrame(AVCodecContext pCodecCtx, AVFormatContext pFormatCtx, int videoStreamIndex) {
        AVFrame pFrame = av_frame_alloc();

        int[] frameFinished = new int[1];

        boolean exists = false;

        AVPacket packet = new AVPacket();

        try {
            while (av_read_frame(pFormatCtx, packet) >= 0) {
                if (packet.stream_index() == videoStreamIndex) {
                    avcodec_decode_video2(pCodecCtx, pFrame, frameFinished, packet);
                    if (frameFinished != null && frameFinished[0] != 0 && !pFrame.isNull()) {
                        exists = true;
                        break;
                    }
                }
            }
        } finally {
            av_packet_unref(packet);
        }

        return exists ? pFrame : null;
    }

    private static FrameData YUV420PToYUVJ420P(AVCodecContext pCodecCtx, AVFrame sourceFrame) {
        AVFrame pFrameRGB = av_frame_alloc();

        if (pFrameRGB == null) {
            return null;
        }

        int width = pCodecCtx.width(), height = pCodecCtx.height();

        pFrameRGB.width(width);
        pFrameRGB.height(height);
        pFrameRGB.format(DEST_FORMAT);

        int numBytes = avpicture_get_size(DEST_FORMAT, width, height);

        BytePointer buffer = new BytePointer(av_malloc(numBytes));

        SwsContext sws_ctx = sws_getContext(width, height, pCodecCtx.pix_fmt(), width, height, DEST_FORMAT, SWS_BICUBIC, null, null, (DoublePointer) null);

        avpicture_fill(new AVPicture(pFrameRGB), buffer, DEST_FORMAT, width, height);

        sws_scale(sws_ctx, sourceFrame.data(), sourceFrame.linesize(), 0, height, pFrameRGB.data(), pFrameRGB.linesize());

        sws_freeContext(sws_ctx);

        return new FrameData(pFrameRGB, buffer);
    }

    private int saveImg(AVFrame pFrame, String out_file) {
        AVPacket pkt = null;
        AVStream pAVStream = null;

        int width = pFrame.width(), height = pFrame.height();

        AVFormatContext pFormatCtx = avformat_alloc_context();

        pFormatCtx.oformat(av_guess_format("mjpeg", null, null));

        if (pFormatCtx.oformat() == null) {
            return -1;
        }

        try {
            AVIOContext pb = new AVIOContext();

            if (avio_open(pb, out_file, AVIO_FLAG_READ_WRITE) < 0) {
                return -1;
            }

            pFormatCtx.pb(pb);

            pAVStream = avformat_new_stream(pFormatCtx, null);

            if (pAVStream == null) {
                return -1;
            }

            int codec_id = pFormatCtx.oformat().video_codec();

            AVCodecContext pCodecCtx = pAVStream.codec();
            pCodecCtx.codec_id(codec_id);
            pCodecCtx.codec_type(AVMEDIA_TYPE_VIDEO);
            pCodecCtx.pix_fmt(DEST_FORMAT);
            pCodecCtx.width(width);
            pCodecCtx.height(height);
            pCodecCtx.time_base().num(1);
            pCodecCtx.time_base().den(25);

            av_dump_format(pFormatCtx, 0, out_file, 1);

            AVCodec pCodec = avcodec_find_encoder(codec_id);
            if (pCodec == null) {
                return -1;
            }

            if (avcodec_open2(pCodecCtx, pCodec, (PointerPointer<Pointer>) null) < 0) {
                return -1;
            }

            pkt = new AVPacket();
            if (av_new_packet(pkt, width * height * 3) < 0) {
                return -1;
            }

            int[] got_picture = {0};

            avformat_write_header(pFormatCtx, (PointerPointer<Pointer>) null);

            if (avcodec_encode_video2(pCodecCtx, pkt, pFrame, got_picture) < 0) {
                return -1;
            }

            if ((av_write_frame(pFormatCtx, pkt)) < 0) {
                return -1;
            }

            if (av_write_trailer(pFormatCtx) < 0) {
                return -1;
            }
            return 0;
        } finally {
            release(false, pkt, pFormatCtx.pb(), pAVStream.codec(), pFormatCtx);
        }
    }

    private void release(boolean isInput, AVPacket pkt, AVIOContext pb, AVCodecContext pCodecCtx, AVFormatContext pFormatCtx, Pointer... ptrs) {
        if (null != pkt) {
            av_packet_unref(pkt);
        }
        if (null != ptrs) {
            Arrays.stream(ptrs).forEach(avutil::av_free);
        }
        if (null != pCodecCtx) {
            avcodec_close(pCodecCtx);
        }
        if (null != pb) {
            avio_close(pb);
        }
        if (null != pFormatCtx) {
            if (isInput) {
                avformat_close_input(pFormatCtx);
            } else {
                avformat_free_context(pFormatCtx);
            }
        }
    }

    private void release(boolean isInput, AVPacket pkt, AVIOContext pb, AVCodecContext pCodecCtx, AVFormatContext pFormatCtx, List<Pointer> ptrs) {
        if (null != pkt) {
            av_packet_unref(pkt);
        }
        if (ptrs != null && !ptrs.isEmpty()) {
            ptrs.forEach(avutil::av_free);
        }

        if (null != pCodecCtx) {
            avcodec_close(pCodecCtx);
        }

        if (null != pb) {
            avio_close(pb);
        }

        if (null != pFormatCtx) {
            if (isInput) {
                avformat_close_input(pFormatCtx);
            } else {
                avformat_free_context(pFormatCtx);
            }
        }
    }
}

class FrameData {

    AVFrame avFrame;
    BytePointer buffer;

    public FrameData(AVFrame avFrame, BytePointer buffer) {
        this.avFrame = avFrame;
        this.buffer = buffer;
    }
}

But every time (not more than 20 minutes), jvm will crash, the log is as follows:

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGFPE (0x8) at pc=0x00007f90c0dbb76c, pid=1, tid=0x00007f90b94d4700
#
# JRE version: OpenJDK Runtime Environment (8.0_111-b14) (build 1.8.0_111-8u111-b14-2~bpo8+1-b14)
# Java VM: OpenJDK 64-Bit Server VM (25.111-b14 mixed mode linux-amd64 compressed oops)
# Problematic frame:
# C  [libavformat.so.58+0x18c76c]
#
# Core dump written. Default location: //core or core.1
#
# Can not save log file, dump to screen..
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGFPE (0x8) at pc=0x00007f90c0dbb76c, pid=1, tid=0x00007f90b94d4700
#
# JRE version: OpenJDK Runtime Environment (8.0_111-b14) (build 1.8.0_111-8u111-b14-2~bpo8+1-b14)
# Java VM: OpenJDK 64-Bit Server VM (25.111-b14 mixed mode linux-amd64 compressed oops)
# Problematic frame:
# C  [libavformat.so.58+0x18c76c]
#
# Core dump written. Default location: //core or core.1
#
# If you would like to submit a bug report, please visit:
#   http://bugreport.java.com/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#

---------------  T H R E A D  ---------------

Current thread (0x00007f8cd013d800):  JavaThread "TaskSchedulerThreadPool-132" [_thread_in_native, id=404, stack(0x00007f90b93d4000,0x00007f90b94d5000)]

siginfo: si_signo: 8 (SIGFPE), si_code: 1 (FPE_INTDIV), si_addr: 0x00007f90c0dbb76c

Registers:
RAX=0x000000000000f520, RBX=0x000000000000f520, RCX=0x00007f8a28000020, RDX=0x0000000000000000
RSP=0x00007f90b94cee60, RBP=0x00007f8a28004dd4, RSI=0x0000000000006949, RDI=0x0000000000000000
R8 =0x0000000000000001, R9 =0x00007f8a280012e0, R10=0x0000000000001c60, R11=0x0000000000000000
R12=0x00007f8a28004dbc, R13=0x00000000000000e9, R14=0x0000000000001ed2, R15=0x00007f8a28003300
RIP=0x00007f90c0dbb76c, EFLAGS=0x0000000000010202, CSGSFS=0x002b000000000033, ERR=0x0000000000000000
  TRAPNO=0x0000000000000000

Top of Stack: (sp=0x00007f90b94cee60)
0x00007f90b94cee60:   0000000000000000 0000000000000000
0x00007f90b94cee70:   00007f8a28003dc0 00007f90b94d3010
0x00007f90b94cee80:   0000000000000000 00007f8a28004ac0
0x00007f90b94cee90:   0000000000001ef2 00007f90b94d3010
0x00007f90b94ceea0:   00007f8a28003dc0 00007f8a28004db4
0x00007f90b94ceeb0:   00007f8a28001340 00007f90c0db2acd
0x00007f90b94ceec0:   0000000000001ef2 000000000000c04a
0x00007f90b94ceed0:   0000000000000000 00007ffeadfee982
0x00007f90b94ceee0:   00007f8a28004ac0 dae954f800000000
0x00007f90b94ceef0:   00007f90b94d3010 00007f8a28004ac0
0x00007f90b94cef00:   00007f90b94d3010 0000000000000000
0x00007f90b94cef10:   0000000000001f40 00007f8a28004d80
0x00007f90b94cef20:   00007f8a28001340 00007f90c0db4041
0x00007f90b94cef30:   00007f8a280031c0 0000000000000000
0x00007f90b94cef40:   0000000000000000 00001f4000000000
0x00007f90b94cef50:   0000000000000000 dae954f800000000
0x00007f90b94cef60:   0000000000000000 0000000000000000
0x00007f90b94cef70:   00007f8a28001b00 0000000000000000
0x00007f90b94cef80:   0000000000000000 00007f8a28001b00
0x00007f90b94cef90:   00007f8a28001340 00007f90c0dc861d
0x00007f90b94cefa0:   0000000000000000 00007f8a28001340
0x00007f90b94cefb0:   0000000000000000 0000000000000000
0x00007f90b94cefc0:   0000000000000000 00007f90b94d3010
0x00007f90b94cefd0:   0000000000000000 0000000000000000
0x00007f90b94cefe0:   00007f90b94cf010 00007f90b94cf00c
0x00007f90b94ceff0:   00007f90b94cf018 0000000000000000
0x00007f90b94cf000:   0000000000000000 0000000000000000
0x00007f90b94cf010:   00007f8a280037c0 0000000000000000
0x00007f90b94cf020:   0000000000000000 00007f8a28001b00
0x00007f90b94cf030:   0000000000000000 00007f8a28001340
0x00007f90b94cf040:   8000000000000000 0000000000000028
0x00007f90b94cf050:   00007f8a28001340 00007f90c0dca974 

Instructions: (pc=0x00007f90c0dbb76c)
0x00007f90c0dbb74c:   e0 08 25 00 7f 00 00 66 c1 c3 08 41 09 c2 44 89
0x00007f90c0dbb75c:   e8 0f b7 db c1 e0 08 25 00 7f 00 00 09 c6 89 d8
0x00007f90c0dbb76c:   f7 f7 85 d2 75 8c 41 8b 47 0c 41 39 de 41 0f 4e
0x00007f90c0dbb77c:   de 31 d2 41 0f af c2 01 f0 0f af c7 41 f7 77 28 

Register to memory mapping:

RAX=0x000000000000f520 is an unknown value
RBX=0x000000000000f520 is an unknown value
RCX=0x00007f8a28000020 is an unknown value
RDX=0x0000000000000000 is an unknown value
RSP=0x00007f90b94cee60 is pointing into the stack for thread: 0x00007f8cd013d800
RBP=0x00007f8a28004dd4 is an unknown value
RSI=0x0000000000006949 is an unknown value
RDI=0x0000000000000000 is an unknown value
R8 =0x0000000000000001 is an unknown value
R9 =0x00007f8a280012e0 is an unknown value
R10=0x0000000000001c60 is an unknown value
R11=0x0000000000000000 is an unknown value
R12=0x00007f8a28004dbc is an unknown value
R13=0x00000000000000e9 is an unknown value
R14=0x0000000000001ed2 is an unknown value
R15=0x00007f8a28003300 is an unknown value

Stack: [0x00007f90b93d4000,0x00007f90b94d5000],  sp=0x00007f90b94cee60,  free space=1003k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
C  [libavformat.so.58+0x18c76c]

Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
j  org.bytedeco.javacpp.avformat.avformat_find_stream_info(Lorg/bytedeco/javacpp/avformat$AVFormatContext;Lorg/bytedeco/javacpp/PointerPointer;)I+0
j  com.analysis.util.Stream2Image.getFormatContext(Ljava/lang/String;)Lorg/bytedeco/javacpp/avformat$AVFormatContext;+55
j  com.analysis.util.Stream2Image.safeOpenMediaAndSaveImage(Ljava/lang/String;Ljava/lang/String;)V+29
j  com.analysis.service.impl.GrabberServiceImpl.grabOne(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;+43
j  com.analysis.task.util.GrabUtil.grabAndSave(Lcom/analysis/pojo/po/Device;Ljava/util/Date;Ljava/lang/String;)Ljava/lang/String;+76
j  com.analysis.task.util.GrabUtil.longDiffTask(Lcom/analysis/pojo/po/Device;)V+35
j  com.analysis.task.util.GrabUtil.lambda$creatCronTask$0(Lcom/analysis/pojo/po/Device;)V+2
j  com.analysis.task.util.GrabUtil$$Lambda$631.run()V+8
j  org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run()V+4
j  org.springframework.scheduling.concurrent.ReschedulingRunnable.run()V+9
j  java.util.concurrent.Executors$RunnableAdapter.call()Ljava/lang/Object;+4
J 6653 C1 java.util.concurrent.FutureTask.run()V (126 bytes) @ 0x00007f9151e6cfe4 [0x00007f9151e6cd80+0x264]
j  java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(Ljava/util/concurrent/ScheduledThreadPoolExecutor$ScheduledFutureTask;)V+1
j  java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run()V+30
j  java.util.concurrent.ThreadPoolExecutor.runWorker(Ljava/util/concurrent/ThreadPoolExecutor$Worker;)V+95
j  java.util.concurrent.ThreadPoolExecutor$Worker.run()V+5
j  java.lang.Thread.run()V+11
v  ~StubRoutines::call_stub

Unfortunately, I'm a novice and don't know C and C + +. I really want to know what the problem is. I hope someone can help me, because I've tried many ways, but I still can't solve it. Thank you very much.

zoooooway commented 2 years ago

These are dependent information:

        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacpp</artifactId>
            <version>1.4.3</version>
        </dependency>
        <dependency>
            <groupId>org.bytedeco.javacpp-presets</groupId>
            <artifactId>ffmpeg-platform</artifactId>
            <version>4.0.2-1.4.3</version>
        </dependency>
saudet commented 2 years ago

You'll need to pass something else than null to avformat_find_stream_info()...

devjeonghwan commented 2 years ago

avformat_find_stream_info second parameter can be null. it is okay. (See link)

I think problem is AVFormatContext pFormatCtx = new AVFormatContext(null); under getFormatContext method.

use instead AVFormatContext pFormatCtx = avformat_alloc_context(); (new AVFormatContext(null) mean is "Casting null to AVFormatContext". See link)

zoooooway commented 2 years ago

You'll need to pass something else than null to avformat_find_stream_info()...

Hello, thank you for your reply. According to your prompt, I guess that there may be problems with some urls that cause problems with the AVFormatContext obtained after executing avformat_open_input(pFormatCtx, url, null, options), because not every url will let The jvm crashes, it can work normally when some URLs execute this program and return screenshots. So I tried to perform some non-null judgments on the internal data of pFormatCtx before executing avformat_find_stream_info(pFormatCtx, (PointerPointer<Pointer>) null), but it didn't seem to work. So I would like to ask if there is any way for me to safely execute avformat_find_stream_info(pFormatCtx, (PointerPointer<Pointer>) null), or terminate it when an error is foreseen, to prevent jvm from crashing. Thank you again for your help.

zoooooway commented 2 years ago

avformat_find_stream_info second parameter can be null. it is okay. (See link)

I think problem is AVFormatContext pFormatCtx = new AVFormatContext(null); under getFormatContext method.

use instead AVFormatContext pFormatCtx = avformat_alloc_context(); (new AVFormatContext(null) mean is "Casting null to AVFormatContext". See link)

Hello, thank you for your reply, I replaced the constructor with the constructor as you said. But now av_dict_set(options, "rtsp_transport", "tcp", 0) does not work properly and crashes the virtual machine.

        AVFormatContext pFormatCtx = avformat_alloc_context();
        AVDictionary options = new AVDictionary(pFormatCtx);
        av_dict_set(options, "rtsp_transport", "tcp", 0);
        if (avformat_open_input(pFormatCtx, url, null, options) != 0) {
            return null;
        }

I need this function to set options, so can you please tell me how to do it? Thank you again for your help.

devjeonghwan commented 2 years ago

Use like this

        AVFormatContext pFormatCtx = avformat_alloc_context();
        AVDictionary options = new AVDictionary();
        av_dict_set(options, "rtsp_transport", "tcp", 0);
        if (avformat_open_input(pFormatCtx, url, null, options) != 0) {
            return null;
        }

Please test it and let me know the result

devjeonghwan commented 2 years ago

Your first code is strange, but it is no problem in working. (When AVFormatContext points to NULL, ffmpeg allocates it.)

        AVFormatContext pFormatCtx = new AVFormatContext(null);
        AVDictionary options = new AVDictionary(pFormatCtx);

But this code is more standard code.

        AVFormatContext pFormatCtx = avformat_alloc_context();
        AVDictionary options = new AVDictionary();

I'm tested using your code snippet. like this

    public static void main(String[] args) throws IOException, InterruptedException {
        Stream2Image stream2Image = new Stream2Image();
        for (int i = 0; i < 100000; i++) {
            stream2Image.safeOpenMediaAndSaveImage("test" + (i % 10) + ".mp4", "test/snap_" + i + ".png");
        }
    }

JVM doesn't crash. It looks like we need more information about error URL or video binary.

zoooooway commented 2 years ago

Your first code is strange, but it is no problem in working. (When AVFormatContext points to NULL, ffmpeg allocates it.)

        AVFormatContext pFormatCtx = new AVFormatContext(null);
        AVDictionary options = new AVDictionary(pFormatCtx);

But this code is more standard code.

        AVFormatContext pFormatCtx = avformat_alloc_context();
        AVDictionary options = new AVDictionary();

I'm tested using your code snippet. like this

    public static void main(String[] args) throws IOException, InterruptedException {
        Stream2Image stream2Image = new Stream2Image();
        for (int i = 0; i < 100000; i++) {
            stream2Image.safeOpenMediaAndSaveImage("test" + (i % 10) + ".mp4", "test/snap_" + i + ".png");
        }
    }

JVM doesn't crash. It looks like we need more information about error URL or video binary.

Hello, sorry for the late reply, thank you very much for everything. As you said, I use

        AVFormatContext pFormatCtx = avformat_alloc_context();
        AVDictionary options = new AVDictionary();
        av_dict_set(options, "rtsp_transport", "tcp", 0);
        if (avformat_open_input(pFormatCtx, url, null, options) != 0) {
            return null;
        }

It can work normally. However, during the running of the program, there will still be the previous situation: jvm crashes when executing avformat_find_stream_info(). Since the program is deployed on a special server, and only this server can access the URLs from another streaming media server and fetch frames and convert them into pictures, I cannot debug locally to find out which URL is causing the crash. This troubles me a lot. Due to these limitations, I wonder if there is a way to execute avformat_find_stream_info() safely, such as a comprehensive parameter check before execution, and if the necessary conditions are missing, the program will not be executed to prevent jvm collapse. If you have any suggestions, please let me know and I will continue to try to change the program. Thank you again for your help.

devjeonghwan commented 2 years ago

I can't say that the avformat_find_stream_info function is the problem. because the avformat_find_stream_info function is implemented to return an error code in case of an error.

In general, it is possible that the JVM crashed is a memory corruption. So, "parameter check for execute safely" is meaningless because maybe the FFmpeg memory is already corrupted.

RTSP/LocalFile stream can be received without calling the avformat_find_stream_info function. Comment out the avformat_find_stream_info call and test it.

saudet commented 2 years ago

@hzw133 Do you get any crashes with FFmpegFrameGrabber?

zoooooway commented 2 years ago

I can't say that the avformat_find_stream_info function is the problem. because the avformat_find_stream_info function is implemented to return an error code in case of an error.

In general, it is possible that the JVM crashed is a memory corruption. So, "parameter check for execute safely" is meaningless because maybe the FFmpeg memory is already corrupted.

RTSP/LocalFile stream can be received without calling the avformat_find_stream_info function. Comment out the avformat_find_stream_info call and test it.

It succeeded. After skipping the avformat_find_stream_info() function, I waited for the program to execute for a while, and the jvm didn't crash anymore (usually it would crash after five to twenty minutes of program execution). Fortunately, the streams I need to process are all RTSP protocols, so I think this solution is effective. thank you for your help. For me, this problem may not be over. I need to handle streams of other protocols in this way in other programs, but it may not be possible to skip the avformat_find_stream_info() function at that time. But now this problem has come to an end, and I will observe whether there will be a crash in the subsequent processing of other agreements. Thank you again and Saudet for your help. This is the first time I asked a question on github. You guys really helped me a lot.

zoooooway commented 2 years ago

@hzw133 Do you get any crashes with FFmpegFrameGrabber?

@hzw133 Do you get any crashes with FFmpegFrameGrabber?

It also crashes when I use FFmpegFrameGrabber. At first I used javacv to complete my work, but then the business became simpler, so I switched to javacpp to perform these operations. When I use FFmpegFrameGrabber to perform these operations, the same crash message still appears, but the frequency of crashes is not high. At that time, I didn't go deep into it but switched to javacpp, so I didn't know what the cause of the crash was at that time. Due to some restrictions, I still don't know the cause of the crash when using javacpp, but according to @devjeonghwan's suggestion I can skip avformat_find_stream_info() to avoid the crash. I will continue to try to find out the reason if I have the opportunity in the follow-up. If I find anything, I will report it to you. Hope I didn't bother you too much, thank you very much for your help and @devjeonghwan , and the framework you provided us.

devjeonghwan commented 2 years ago

Maybe crash frequency may have been reduced(solved the fundamentally error?). Use the standard usage of ffmpeg(standard way to create and release resources.)

zoooooway commented 2 years ago

hi @saudet @devjeonghwan ,first of all, thank you for your help last time. I'm sorry I'm back again. Now I have a new problem. The program code shown above will get an error after running for a long time:

2021-10-16 16:10:32.551 [TaskSchedulerThreadPool-1234] ERROR o.s.s.support.TaskUtils$LoggingErrorHandler:96 - Unexpected error occurred in scheduled task.
java.lang.OutOfMemoryError: Physical memory usage is too high: physicalBytes (54635M) > maxPhysicalBytes (54610M)
    at org.bytedeco.javacpp.Pointer.deallocator(Pointer.java:584)
    at org.bytedeco.javacpp.Pointer.init(Pointer.java:124)
    at org.bytedeco.javacpp.avcodec$AVPacket.allocate(Native Method)
    at org.bytedeco.javacpp.avcodec$AVPacket.<init>(avcodec.java:1516)
    at com.analysis.util.Stream2Image.getSingleFrame(Stream2Image.java:299)
    at com.analysis.util.Stream2Image.safeOpenMediaAndSaveImage(Stream2Image.java:151)
    at com.analysis.service.impl.GrabberServiceImpl.grabOne(GrabberServiceImpl.java:92)
    at com.analysis.task.util.GrabUtil.grabAndSave(GrabUtil.java:204)
    at com.analysis.task.util.GrabUtil.longDiffTask(GrabUtil.java:187)
    at com.analysis.task.util.GrabUtil.lambda$creatCronTask$0(GrabUtil.java:102)
    at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
    at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:93)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

I found similar errors in other issues, so I tried to use pointerscope to solve the problem of memory growth, but it didn't seem to work. In my test code, I found that the memory was still growing slowly every time my code was executed:

try (PointerScope pointerScope = new PointerScope()) {
       safeOpenMediaAndSaveImage(url, outFile);
}
log.info("physicalBytes:{}; totalBytes:{}", Pointer.physicalBytes(), Pointer.totalBytes());
....
physicalBytes:236363776; totalBytes:9504
physicalBytes:236478464; totalBytes:9936
physicalBytes:236498944; totalBytes:10368
....

After calling System.gc() manually, totalBytes no longer increases, but physicalBytes still increases after each execution, and the following is the increase of half an hour:

physicalBytes:236498944
....
physicalBytes:259645440
...
physicalBytes:261640192
...
physicalBytes:274259968
...
physicalBytes:331444224

So, is there something missing from my code? I guess my memory release related operation is not perfect, but I don't know what the problem is. I hope you can give me some suggestions.

Thank you.

saudet commented 2 years ago

FFmpeg is a C API, and it's quite tricky to deallocate everything properly. You'll need to read the API docs very carefully.

zoooooway commented 2 years ago

FFmpeg is a C API, and it's quite tricky to deallocate everything properly. You'll need to read the API docs very carefully.

This is not easy for me, but I will try my best to read it, thank you for your suggestion.

devjeonghwan commented 2 years ago

FFmpeg is a C API, and it's quite tricky to deallocate everything properly. You'll need to read the API docs very carefully.

This is not easy for me, but I will try my best to read it, thank you for your suggestion.

I found one problem usage in your getSingleFrame method.

Before

    private AVFrame getSingleFrame(AVCodecContext pCodecCtx, AVFormatContext pFormatCtx, int videoStreamIndex) {
        ...

        AVPacket packet = new AVPacket();

        try {
            while (av_read_frame(pFormatCtx, packet) >= 0) {
                if (packet.stream_index() == videoStreamIndex) {
                    avcodec_decode_video2(pCodecCtx, pFrame, frameFinished, packet);
                    if (frameFinished != null && frameFinished[0] != 0 && !pFrame.isNull()) {
                        exists = true;
                        break;
                    }
                }
            }
        } finally {
            av_packet_unref(packet);
        }

        return exists ? pFrame : null;
    }

After

    private AVFrame getSingleFrame(AVCodecContext pCodecCtx, AVFormatContext pFormatCtx, int videoStreamIndex) {
        ...

        AVPacket packet = av_packet_alloc();
        av_init_packet(packet);

        try {
            while (av_read_frame(pFormatCtx, packet) >= 0) {
                try {
                    if (packet.stream_index() == videoStreamIndex) {
                        avcodec_decode_video2(pCodecCtx, pFrame, frameFinished, packet);
                        if (frameFinished != null && frameFinished[0] != 0 && !pFrame.isNull()) {
                            exists = true;
                            break;
                        }
                    }
                } finally {
                    av_packet_unref(packet);
                }
            }
        } finally {
            av_packet_unref(packet);
            av_packet_free(packet);
        }

        return exists ? pFrame : null;
    }

But, I can't go through all the code. Read the FFmpeg API Doc carefully. (Correct objects allocate way and correct timing or way of deallocate)

zoooooway commented 2 years ago

hi, @devjeonghwan. I’m sorry it took me so long to respond. First of all, thank you for taking the time to help me. I fixed the problem you pointed out and tried to run it for a while. Unfortunately, memory is still growing slowly. But this points out some of the irregularities in my use of FFMEP, and I want to first try to replace some outdated apis and more debugging to see the details of the memory increase. Thank you so much for your advice. If you have any further suggestions, please let me know. By the way, I think the learning of FFMEP is really hard, which makes me think you are really strong.