diff --git a/CHANGELOG.md b/CHANGELOG.md index eb119d14..cbfc4517 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ + * Increase thread safety of `FFmpegFrameFilter`, `FFmpegFrameGrabber`, and `FFmpegFrameRecorder` with `volatile boolean started` flag ([pull #1325](https://github.com/bytedeco/javacv/pull/1325)) * Let `FFmpegFrameFilter.push(null)` indicate EOF to audio filters as well ([issue #1315](https://github.com/bytedeco/javacv/issues/1315)) * Add `RealSense2FrameGrabber` to capture images with librealsense2 ([pull #1316](https://github.com/bytedeco/javacv/pull/1316)) * Disable seek function in `FFmpegFrameGrabber` when `maximumSize <= 0` ([issue #1304](https://github.com/bytedeco/javacv/issues/1304)) diff --git a/src/main/java/org/bytedeco/javacv/FFmpegFrameFilter.java b/src/main/java/org/bytedeco/javacv/FFmpegFrameFilter.java index 2d7b1508..ed4da749 100644 --- a/src/main/java/org/bytedeco/javacv/FFmpegFrameFilter.java +++ b/src/main/java/org/bytedeco/javacv/FFmpegFrameFilter.java @@ -145,6 +145,8 @@ public FFmpegFrameFilter(String afilters, int audioChannels) { } } public void releaseUnsafe() throws Exception { + started = false; + if (filter_graph != null) { avfilter_graph_free(filter_graph); buffersink_ctx = null; @@ -202,6 +204,8 @@ public void releaseUnsafe() throws Exception { Buffer[] samples_buf; Frame frame, inframe; + private volatile boolean started = false; + @Override public int getImageWidth() { return buffersink_ctx != null ? av_buffersink_get_w(buffersink_ctx) : super.getImageWidth(); } @@ -277,6 +281,8 @@ public void startUnsafe() throws Exception { if (afilters != null && audioChannels > 0 && audioInputs > 0) { startAudioUnsafe(); } + + started = true; } void startVideoUnsafe() throws Exception { @@ -493,6 +499,10 @@ public void push(int n, Frame frame) throws Exception { push(n, frame, frame != null && frame.opaque instanceof AVFrame ? ((AVFrame)frame.opaque).format() : AV_PIX_FMT_NONE); } public void push(int n, Frame frame, int pixelFormat) throws Exception { + if (!started) { + throw new Exception("start() was not called successfully!"); + } + inframe = frame; if (frame != null && frame.image != null && buffersrc_ctx != null) { image_frame.pts(frame.timestamp * time_base.den() / (1000000L * time_base.num())); @@ -515,6 +525,10 @@ public void push(int n, Frame frame, int pixelFormat) throws Exception { } public void pushImage(int n, int width, int height, int depth, int channels, int stride, int pixelFormat, Buffer ... image) throws Exception { + if (!started) { + throw new Exception("start() was not called successfully!"); + } + int ret; int step = stride * Math.abs(depth) / 8; BytePointer data = image[0] instanceof ByteBuffer @@ -555,6 +569,10 @@ public void pushImage(int n, int width, int height, int depth, int channels, int } public void pushSamples(int n, int audioChannels, int sampleRate, int sampleFormat, Buffer ... samples) throws Exception { + if (!started) { + throw new Exception("start() was not called successfully!"); + } + int ret; Pointer[] data = new Pointer[samples.length]; int sampleSize = samples != null ? ((samples[0].limit() - samples[0].position()) / (samples.length > 1 ? 1 : audioChannels)) : 0; @@ -606,6 +624,10 @@ public void pushSamples(int n, int audioChannels, int sampleRate, int sampleForm } @Override public Frame pull() throws Exception { + if (!started) { + throw new Exception("start() was not called successfully!"); + } + frame.keyFrame = false; frame.imageWidth = 0; frame.imageHeight = 0; @@ -633,6 +655,10 @@ public void pushSamples(int n, int audioChannels, int sampleRate, int sampleForm } public Frame pullImage() throws Exception { + if (!started) { + throw new Exception("start() was not called successfully!"); + } + av_frame_unref(filt_frame); /* pull a filtered frame from the filtergraph */ @@ -678,6 +704,10 @@ public Frame pullImage() throws Exception { } public Frame pullSamples() throws Exception { + if (!started) { + throw new Exception("start() was not called successfully!"); + } + av_frame_unref(filt_frame); /* pull a filtered frame from the filtergraph */ diff --git a/src/main/java/org/bytedeco/javacv/FFmpegFrameGrabber.java b/src/main/java/org/bytedeco/javacv/FFmpegFrameGrabber.java index 7a9342d0..bd0142e1 100644 --- a/src/main/java/org/bytedeco/javacv/FFmpegFrameGrabber.java +++ b/src/main/java/org/bytedeco/javacv/FFmpegFrameGrabber.java @@ -157,6 +157,7 @@ public void release() throws Exception { } } public void releaseUnsafe() throws Exception { + started = false; if (pkt != null && pkt2 != null) { if (pkt2.size() > 0) { av_packet_unref(pkt); @@ -364,6 +365,8 @@ static class SeekCallback extends Seek_Pointer_long_int { private boolean frameGrabbed; private Frame frame; + private volatile boolean started = false; + public boolean isCloseInputStream() { return closeInputStream; } @@ -976,6 +979,7 @@ public void startUnsafe() throws Exception { samples_ptr = new BytePointer[] { null }; samples_buf = new Buffer[] { null }; } + started = true; } private void initPictureRGB() { @@ -1216,6 +1220,10 @@ public Frame grabFrame(boolean doAudio, boolean doVideo, boolean doProcessing, b } else if ((!doVideo || video_st == null) && (!doAudio || audio_st == null)) { return null; } + if (!started) { + throw new Exception("start() was not called successfully!"); + } + boolean videoFrameGrabbed = frameGrabbed && frame.image != null; boolean audioFrameGrabbed = frameGrabbed && frame.samples != null; frameGrabbed = false; @@ -1324,7 +1332,10 @@ public Frame grabFrame(boolean doAudio, boolean doVideo, boolean doProcessing, b public AVPacket grabPacket() throws Exception { if (oc == null || oc.isNull()) { - throw new Exception("Could not trigger: No AVFormatContext. (Has start() been called?)"); + throw new Exception("Could not grab: No AVFormatContext. (Has start() been called?)"); + } + if (!started) { + throw new Exception("start() was not called successfully!"); } // Return the next frame of a stream. diff --git a/src/main/java/org/bytedeco/javacv/FFmpegFrameRecorder.java b/src/main/java/org/bytedeco/javacv/FFmpegFrameRecorder.java index 62591f0f..9bffe9f9 100644 --- a/src/main/java/org/bytedeco/javacv/FFmpegFrameRecorder.java +++ b/src/main/java/org/bytedeco/javacv/FFmpegFrameRecorder.java @@ -188,6 +188,8 @@ public void release() throws Exception { } } public void releaseUnsafe() throws Exception { + started = false; + /* close each codec */ if (video_c != null) { avcodec_free_context(video_c); @@ -348,6 +350,8 @@ static class WriteCallback extends Write_packet_Pointer_BytePointer_int { private int[] got_video_packet, got_audio_packet; private AVFormatContext ifmt_ctx; + private volatile boolean started = false; + public boolean isCloseOutputStream() { return closeOutputStream; } @@ -370,8 +374,8 @@ public void setCloseOutputStream(boolean closeOutputStream) { setFrameNumber((int)Math.round(timestamp * getFrameRate() / 1000000L)); } - public void start(AVFormatContext ifmt_ctx) throws Exception { - this.ifmt_ctx = ifmt_ctx; + public void start(AVFormatContext inputFormatContext) throws Exception { + this.ifmt_ctx = inputFormatContext; start(); } @@ -862,12 +866,18 @@ public void startUnsafe() throws Exception { av_dict_set(metadata, e.getKey(), e.getValue(), 0); } /* write the stream header, if any */ - avformat_write_header(oc.metadata(metadata), options); + if ((ret = avformat_write_header(oc.metadata(metadata), options)) < 0) { + releaseUnsafe(); + av_dict_free(options); + throw new Exception("avformat_write_header error() error " + ret + ": Could not write header to '" + filename + "'"); + } av_dict_free(options); if (av_log_get_level() >= AV_LOG_INFO) { av_dump_format(oc, 0, filename, 1); } + + started = true; } public void flush() throws Exception { @@ -918,6 +928,9 @@ public boolean recordImage(int width, int height, int depth, int channels, int s if (video_st == null) { throw new Exception("No video output stream (Is imageWidth > 0 && imageHeight > 0 and has start() been called?)"); } + if (!started) { + throw new Exception("start() was not called successfully!"); + } int ret; if (image == null || image.length == 0) { @@ -1026,6 +1039,9 @@ public boolean recordSamples(int sampleRate, int audioChannels, Buffer ... sampl if (audio_st == null) { throw new Exception("No audio output stream (Is audioChannels > 0 and has start() been called?)"); } + if (!started) { + throw new Exception("start() was not called successfully!"); + } if (samples == null && samples_out[0].position() > 0) { // Typically samples_out[0].limit() is double the audio_input_frame_size --> sampleDivisor = 2 @@ -1230,6 +1246,12 @@ private void writePacket(int mediaType, AVPacket avPacket) throws Exception { } public boolean recordPacket(AVPacket pkt) throws Exception { + if (ifmt_ctx == null) { + throw new Exception("No input format context (Has start(AVFormatContext) been called?)"); + } + if (!started) { + throw new Exception("start() was not called successfully!"); + } if (pkt == null) { return false;