Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FFMpegFrameGrabber.stop() Causes JVM Crash #1434

Closed
MarcWoodyard opened this issue May 29, 2020 · 3 comments
Closed

FFMpegFrameGrabber.stop() Causes JVM Crash #1434

MarcWoodyard opened this issue May 29, 2020 · 3 comments
Labels

Comments

@MarcWoodyard
Copy link

MarcWoodyard commented May 29, 2020

I have a recording object thread that contains a frame recorder and a frame grabber. When it's time to interrupt the thread, calling java this.frameGrabber.stop(); causes the JVM to crash. When I comment it out, the JVM doesn't crash anymore.

 @Override
    public void interrupt() {
        try {
            this.subThread.interrupt();

            if (this.frameRecorder != null)
                this.frameRecorder.stop();

            this.frameGrabber.stop();  // Causes JVM crash
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

Full File:

public class FFMPEGRecorder extends Thread {

    private static DateTimeFormatter fileFormatter = DateTimeFormatter.ofPattern("MM-dd-yyyy hh:mm:ss", Locale.ENGLISH);
    private static DateTimeFormatter directoryFormatter = DateTimeFormatter.ofPattern("MM-dd-yyyy", Locale.ENGLISH);
    private FFmpegFrameGrabber frameGrabber;
    private FFmpegFrameRecorder frameRecorder;
    private CameraScreenshot screenshotThread = new CameraScreenshot();
    private Thread subThread;
    private int id;
    private String cameraName;
    private String streamURL;
    private LocalDateTime start;
    private LocalDateTime end;
    private boolean recordingEnabled;

    public FFMPEGRecorder(int id, String cameraName, String streamURL) {
        this.id = id;
        this.cameraName = cameraName;
        this.streamURL = streamURL;
        this.recordingEnabled = CameraManager.getCamera(id).getRecordingEnabled();
    }

    @Override
    public final void start() {
        try {
            this.frameGrabber = new FFmpegFrameGrabber(this.streamURL);

            System.out.println(this.streamURL);

            this.frameGrabber.setVideoCodec(8);

            if (streamURL.contains("rtsp")) {
                this.frameGrabber.setFormat("rtsp");
                this.frameGrabber.setOption("rtsp_transport", "tcp");
            } else if (streamURL.contains("mjpg"))
                this.frameGrabber.setFormat("mjpeg");
            
            this.frameGrabber.setFrameRate(30);

            this.frameGrabber.start();

            if (this.recordingEnabled)
                this.createNewRecording();

            this.subThread = new Thread(() -> {
                Frame frame;
                while (true) {
                    try {
                        if (this.recordingEnabled && LocalDateTime.now().isAfter(this.end)) {
                            this.frameRecorder.stop();
                            this.createNewRecording();
                        }

                        frame = this.frameGrabber.grab();
                        this.screenshotThread.updateImage(frame);

                        if (this.recordingEnabled && frame != null)
                            this.frameRecorder.record(frame);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });

            this.subThread.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void createNewRecording() throws FrameRecorder.Exception {
        this.start = LocalDateTime.now();
        this.end = LocalDateTime.now().plusMinutes(2);

        File recordingDir = new File(ProgramSettings.getRecordingDirectory() +
                File.separator +
                this.cameraName +
                File.separator +
                directoryFormatter.format(this.start));

        if (!recordingDir.exists())
            recordingDir.mkdirs();

        String curDir = recordingDir.getAbsoluteFile() +
                File.separator +
                cameraName + " [" + fileFormatter.format(this.start) + "] - [" + fileFormatter.format(this.end) + "].webm";

        this.frameRecorder = new FFmpegFrameRecorder(
                curDir,
                this.frameGrabber.getImageWidth(),
                this.frameGrabber.getImageHeight());

        this.frameRecorder.setVideoCodec(avcodec.AV_CODEC_ID_VP9);
        this.frameRecorder.setFormat("webm");
        this.frameRecorder.setFrameRate(this.frameGrabber.getFrameRate());

        this.frameRecorder.start();
    }

    public BufferedImage getScreenshot() {
        return this.screenshotThread.getCurrentImage();
    }

    @Override
    public void interrupt() {
        try {
            this.subThread.interrupt();

            if (this.frameRecorder != null)
                this.frameRecorder.stop();

            this.frameGrabber.stop();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
@saudet
Copy link
Member

saudet commented May 29, 2020

FFmpegFrameGrabber isn't thread safe. You'll need to synchronize calls.

@MarcWoodyard
Copy link
Author

I think I solved it. I changed private FFmpegFrameGrabber frameGrabber; to private FrameGrabber frameGrabber;.

And I changed this.frameGrabber = new FFmpegFrameGrabber(this.streamURL); to this.frameGrabber = new IPCameraFrameGrabber(this.streamURL, 3, 3, TimeUnit.SECONDS);

public class FFMPEGRecorder extends Thread {

    private static DateTimeFormatter fileFormatter = DateTimeFormatter.ofPattern("MM-dd-yyyy hh:mm:ss", Locale.ENGLISH);
    private static DateTimeFormatter directoryFormatter = DateTimeFormatter.ofPattern("MM-dd-yyyy", Locale.ENGLISH);
    private FrameGrabber frameGrabber;
    private FrameRecorder frameRecorder;
    private CameraScreenshot screenshotThread = new CameraScreenshot();
    private Thread subThread;
    private int id;
    private String cameraName;
    private String streamURL;
    private LocalDateTime start;
    private LocalDateTime end;
    private boolean recordingEnabled;

    public FFMPEGRecorder(int id, String cameraName, String streamURL) {
        this.id = id;
        this.cameraName = cameraName;
        this.streamURL = streamURL;
        this.recordingEnabled = CameraManager.getCamera(id).getRecordingEnabled();
    }

    @Override
    public final void start() {
        try {
            this.frameGrabber = new IPCameraFrameGrabber(this.streamURL, 3, 3, TimeUnit.SECONDS);

            this.frameGrabber.setVideoCodec(8);

            if (streamURL.contains("rtsp")) {
                this.frameGrabber.setFormat("rtsp");
                this.frameGrabber.setOption("rtsp_transport", "tcp");
            } else if (streamURL.contains("mjpg"))
                this.frameGrabber.setFormat("mjpeg");

            this.frameGrabber.setFrameRate(30);

            this.frameGrabber.start();

            if (this.recordingEnabled)
                this.createNewRecording();

            this.subThread = new Thread(() -> {
                Frame frame;
                while (true) {
                    try {
                        if (this.recordingEnabled && LocalDateTime.now().isAfter(this.end)) {
                            this.frameRecorder.stop();
                            this.createNewRecording();
                        }

                        frame = this.frameGrabber.grab();

                        if(frame != null) {
                            this.screenshotThread.updateImage(frame);

                            if (this.recordingEnabled)
                                this.frameRecorder.record(frame);
                        }
                    } catch (NullPointerException ignored) {
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });

            this.subThread.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void createNewRecording() throws FrameRecorder.Exception {
        this.start = LocalDateTime.now();
        this.end = LocalDateTime.now().plusMinutes(2);

        File recordingDir = new File(ProgramSettings.getRecordingDirectory() +
                File.separator +
                this.cameraName +
                File.separator +
                directoryFormatter.format(this.start));

        if (!recordingDir.exists())
            recordingDir.mkdirs();

        String curDir = recordingDir.getAbsoluteFile() +
                File.separator +
                cameraName + " [" + fileFormatter.format(this.start) + "] - [" + fileFormatter.format(this.end) + "].webm";

        this.frameRecorder = new FFmpegFrameRecorder(
                curDir,
                this.frameGrabber.getImageWidth(),
                this.frameGrabber.getImageHeight());

        this.frameRecorder.setVideoCodec(avcodec.AV_CODEC_ID_VP9);
        this.frameRecorder.setFormat("webm");
        this.frameRecorder.setFrameRate(this.frameGrabber.getFrameRate());

        this.frameRecorder.start();
    }

    public BufferedImage getScreenshot() {
        return this.screenshotThread.getCurrentImage();
    }

    @Override
    public void interrupt() {
        try {
            this.subThread.interrupt();

            if (this.frameRecorder != null)
                this.frameRecorder.stop();

            this.frameGrabber.close();
            this.frameGrabber.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

@saudet
Copy link
Member

saudet commented May 30, 2020

Good to know that one is thread safe!

/cc @supertick

saudet added a commit that referenced this issue Jun 8, 2020
…`, and `FFmpegFrameRecorder` with `synchronized` methods (issue #1434)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants