Jaffree
Jaffree copied to clipboard
Exceptions when testing stop methods with Capture input
As requested in https://github.com/kokorin/Jaffree/pull/88#issuecomment-654590506 , here is sample code and output demonstrating exceptions being thrown when stopping ffmpeg input with different methods. Context: this is with the CAPTURE_INPUT branch, up-to-date as of today. Basically:
- when the input is ffmpeg's "testsrc", no exception is thrown and no stacktrace appears on the console
- when the input is desktop capture, an exception is thrown with
stopForcefully()
, and stacktraces appear on the console forstopWithException()
andstopWithInterruption()
.stopGracefully()
is OK.
Probably the output should be hidden or at least not propagated up.
Regarding the resulting files:
- the 3 video files generated with the methods that threw an exception (caught or not) cannot be played by VLC
- the 5 others are OK.
I think this is perfectly acceptable, as graceful stop is the normal way of stopping a capture, and all others are "brutal".
Here is the test code;
package examples.ffmpeg;
import com.github.kokorin.jaffree.ffmpeg.*;
import java.nio.file.Paths;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
public class Stop {
public static void stopWithException(final FFmpeg ffmpeg) throws Exception {
final AtomicBoolean stopped = new AtomicBoolean();
ffmpeg.setProgressListener(
new ProgressListener() {
@Override
public void onProgress(FFmpegProgress progress) {
if (stopped.get()) {
throw new RuntimeException("Stopped with exception!");
}
}
}
);
final AtomicReference<FFmpegResult> result = new AtomicReference<>();
new Thread() {
@Override
public void run() {
FFmpegResult r = ffmpeg.execute();
result.set(r);
}
}.start();
Thread.sleep(5_000);
stopped.set(true);
Thread.sleep(1_000);
System.out.println(result.get());
}
public static void stopWithInterruption(final FFmpeg ffmpeg) throws Exception {
final AtomicReference<FFmpegResult> result = new AtomicReference<>();
Thread thread = new Thread() {
@Override
public void run() {
FFmpegResult r = ffmpeg.execute();
result.set(r);
}
};
thread.start();
Thread.sleep(5_000);
thread.interrupt();
Thread.sleep(1_000);
System.out.println(result.get());
}
public static void stopForcefully(final FFmpeg ffmpeg) throws Exception {
FFmpegResultFuture future = ffmpeg.executeAsync();
Thread.sleep(5_000);
future.forceStop();
Thread.sleep(1_000);
System.out.println(future.get());
// Uncaught exception in executeAsync thread:
// Process execution has ended with non-zero status: 1
}
public static void stopGracefully(final FFmpeg ffmpeg) throws Exception {
FFmpegResultFuture future = ffmpeg.executeAsync();
Thread.sleep(5_000);
future.graceStop();
Thread.sleep(1_000);
System.out.println(future.get());
}
public static void main(String[] args) throws Exception {
System.out.println("\n-----------------------------TESTSRC-----------------------------------");
performStopTests(false);
System.out.println("\n-----------------------------DESKTOP----------------------------------");
performStopTests(true);
}
private static void performStopTests(boolean isDesktopSource) throws Exception {
FFmpeg ffmpeg;
System.out.println("\n------- stopWithException -------");
ffmpeg = createTestFFmpeg(isDesktopSource,1);
stopWithException(ffmpeg);
System.out.println("\n------- stopWithInterruption -------");
ffmpeg = createTestFFmpeg(isDesktopSource, 2);
stopWithInterruption(ffmpeg);
try {
System.out.println("\n------- stopForcefully -------");
ffmpeg = createTestFFmpeg(isDesktopSource, 3);
stopForcefully(ffmpeg);
}
catch (ExecutionException e) {
System.err.println("\n!!======= EXCEPTION CAUGHT BY THE CALLER ======!!\n");
e.printStackTrace();
}
System.out.println("\n------- stopGracefully -------");
ffmpeg = createTestFFmpeg(isDesktopSource, 4);
stopGracefully(ffmpeg);
}
public static FFmpeg createTestFFmpeg(boolean isDesktopSource, int n) {
if (isDesktopSource) {
return FFmpeg.atPath()
.addInput(CaptureInput
.captureDesktop()
.setCaptureFrameRate(25)
.setCaptureCursor(true)
)
.setProgressListener(new ProgressListener() {
@Override
public void onProgress(FFmpegProgress progress) {
System.out.println(progress);
}
})
.addOutput(UrlOutput
.toPath(Paths.get("desktop" + n + ".mp4"))
.addArguments("-preset", "ultrafast")
.setDuration(10, TimeUnit.SECONDS)
)
.setOverwriteOutput(true);
}
else {
return FFmpeg.atPath()
.addInput(
UrlInput
.fromUrl("testsrc=duration=3600:size=1280x720:rate=30")
.setFormat("lavfi")
)
.setProgressListener(new ProgressListener() {
@Override
public void onProgress(FFmpegProgress progress) {
System.out.println(progress);
}
})
.addOutput(UrlOutput
.toPath(Paths.get("test" + n + ".mp4"))
.addArguments("-preset", "ultrafast")
.setDuration(10, TimeUnit.SECONDS)
)
.setOverwriteOutput(true);
}
}
}
And here is the console output :
C:\Java\openjdk-14.0.1\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2020.1.1\lib\idea_rt.jar=60826:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2020.1.1\bin" -Dfile.encoding=UTF-8 -classpath "D:\Important\Documents Vincent\Programmation\Java\Jaffree\target\test-classes;D:\Important\Documents Vincent\Programmation\Java\Jaffree\target\classes;C:\Users\Vincent\.m2\repository\org\slf4j\slf4j-api\1.7.25\slf4j-api-1.7.25.jar;C:\Users\Vincent\.m2\repository\org\slf4j\slf4j-log4j12\1.7.25\slf4j-log4j12-1.7.25.jar;C:\Users\Vincent\.m2\repository\log4j\log4j\1.2.17\log4j-1.2.17.jar;C:\Users\Vincent\.m2\repository\commons-io\commons-io\2.5\commons-io-2.5.jar;C:\Users\Vincent\.m2\repository\commons-cli\commons-cli\1.4\commons-cli-1.4.jar;C:\Users\Vincent\.m2\repository\junit\junit\4.12\junit-4.12.jar;C:\Users\Vincent\.m2\repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar" examples.ffmpeg.Stop
-----------------------------TESTSRC-----------------------------------
------- stopWithException -------
INFO Thread-0 com.github.kokorin.jaffree.process.ProcessHandler - Command constructed:
ffmpeg -f lavfi -i testsrc=duration=3600:size=1280x720:rate=30 -y -t 10.000 -preset ultrafast test1.mp4
INFO Thread-0 com.github.kokorin.jaffree.process.ProcessHandler - Starting process: ffmpeg
INFO Thread-0 com.github.kokorin.jaffree.process.ProcessHandler - Waiting for process to finish
INFO Thread-0 com.github.kokorin.jaffree.process.ProcessHandler - Process has finished with status: 0
FFmpegResult{videoSize=280000, audioSize=0, subtitleSize=0, otherStreamsSize=0, globalHeadersSize=0, muxingOverheadRatio=0.00712398}
------- stopWithInterruption -------
INFO Thread-1 com.github.kokorin.jaffree.process.ProcessHandler - Command constructed:
ffmpeg -f lavfi -i testsrc=duration=3600:size=1280x720:rate=30 -y -t 10.000 -preset ultrafast test2.mp4
INFO Thread-1 com.github.kokorin.jaffree.process.ProcessHandler - Starting process: ffmpeg
INFO Thread-1 com.github.kokorin.jaffree.process.ProcessHandler - Waiting for process to finish
FFmpegProgress{frame=45, fps=0.0, q=15.0, size=null, time=1030, dup=null, drop=null, bitrate=0.4, speed=2.05}
FFmpegProgress{frame=96, fps=95.0, q=15.0, size=null, time=2730, dup=null, drop=null, bitrate=0.1, speed=2.7}
FFmpegProgress{frame=145, fps=96.0, q=15.0, size=null, time=4360, dup=null, drop=null, bitrate=0.1, speed=2.89}
FFmpegProgress{frame=191, fps=95.0, q=15.0, size=null, time=5900, dup=null, drop=null, bitrate=0.1, speed=2.93}
FFmpegProgress{frame=235, fps=93.0, q=15.0, size=null, time=7360, dup=null, drop=null, bitrate=0.1, speed=2.93}
FFmpegProgress{frame=284, fps=94.0, q=15.0, size=null, time=9000, dup=null, drop=null, bitrate=233.1, speed=2.99}
FFmpegProgress{frame=300, fps=93.0, q=-1.0, size=282000, time=9960, dup=null, drop=null, bitrate=231.8, speed=3.1}
INFO Thread-1 com.github.kokorin.jaffree.process.ProcessHandler - Process has finished with status: 0
FFmpegResult{videoSize=280000, audioSize=0, subtitleSize=0, otherStreamsSize=0, globalHeadersSize=0, muxingOverheadRatio=0.00712398}
------- stopForcefully -------
INFO FFmpeg-async-runner com.github.kokorin.jaffree.process.ProcessHandler - Command constructed:
ffmpeg -f lavfi -i testsrc=duration=3600:size=1280x720:rate=30 -y -t 10.000 -preset ultrafast test3.mp4
INFO FFmpeg-async-runner com.github.kokorin.jaffree.process.ProcessHandler - Starting process: ffmpeg
INFO FFmpeg-async-runner com.github.kokorin.jaffree.process.ProcessHandler - Waiting for process to finish
FFmpegProgress{frame=47, fps=0.0, q=15.0, size=null, time=1100, dup=null, drop=null, bitrate=0.3, speed=2.2}
FFmpegProgress{frame=97, fps=97.0, q=15.0, size=null, time=2760, dup=null, drop=null, bitrate=0.1, speed=2.76}
FFmpegProgress{frame=145, fps=96.0, q=15.0, size=null, time=4360, dup=null, drop=null, bitrate=0.1, speed=2.9}
FFmpegProgress{frame=193, fps=96.0, q=15.0, size=null, time=5960, dup=null, drop=null, bitrate=0.1, speed=2.97}
FFmpegProgress{frame=243, fps=97.0, q=17.0, size=null, time=7630, dup=null, drop=null, bitrate=0.1, speed=3.03}
FFmpegProgress{frame=295, fps=98.0, q=15.0, size=null, time=9360, dup=null, drop=null, bitrate=223.9, speed=3.1}
FFmpegProgress{frame=300, fps=97.0, q=-1.0, size=282000, time=9960, dup=null, drop=null, bitrate=231.8, speed=3.22}
INFO FFmpeg-async-runner com.github.kokorin.jaffree.process.ProcessHandler - Process has finished with status: 0
FFmpegResult{videoSize=280000, audioSize=0, subtitleSize=0, otherStreamsSize=0, globalHeadersSize=0, muxingOverheadRatio=0.00712398}
------- stopGracefully -------
INFO FFmpeg-async-runner com.github.kokorin.jaffree.process.ProcessHandler - Command constructed:
ffmpeg -f lavfi -i testsrc=duration=3600:size=1280x720:rate=30 -y -t 10.000 -preset ultrafast test4.mp4
INFO FFmpeg-async-runner com.github.kokorin.jaffree.process.ProcessHandler - Starting process: ffmpeg
INFO FFmpeg-async-runner com.github.kokorin.jaffree.process.ProcessHandler - Waiting for process to finish
FFmpegProgress{frame=46, fps=0.0, q=15.0, size=null, time=1060, dup=null, drop=null, bitrate=0.4, speed=2.1}
FFmpegProgress{frame=98, fps=97.0, q=15.0, size=null, time=2800, dup=null, drop=null, bitrate=0.1, speed=2.78}
FFmpegProgress{frame=148, fps=98.0, q=15.0, size=null, time=4460, dup=null, drop=null, bitrate=0.1, speed=2.95}
FFmpegProgress{frame=199, fps=98.0, q=15.0, size=null, time=6160, dup=null, drop=null, bitrate=0.1, speed=3.05}
FFmpegProgress{frame=250, fps=99.0, q=15.0, size=null, time=7860, dup=null, drop=null, bitrate=0.0, speed=3.11}
FFmpegProgress{frame=298, fps=98.0, q=15.0, size=null, time=9460, dup=null, drop=null, bitrate=221.6, speed=3.12}
FFmpegProgress{frame=300, fps=97.0, q=-1.0, size=282000, time=9960, dup=null, drop=null, bitrate=231.8, speed=3.23}
INFO FFmpeg-async-runner com.github.kokorin.jaffree.process.ProcessHandler - Process has finished with status: 0
INFO main com.github.kokorin.jaffree.ffmpeg.FFmpegStopper - Ignoring class java.io.IOException: Stream Closed
FFmpegResult{videoSize=280000, audioSize=0, subtitleSize=0, otherStreamsSize=0, globalHeadersSize=0, muxingOverheadRatio=0.00712398}
-----------------------------DESKTOP----------------------------------
------- stopWithException -------
INFO Thread-2 com.github.kokorin.jaffree.process.ProcessHandler - Command constructed:
ffmpeg -f gdigrab -framerate 25 -draw_mouse 1 -i desktop -y -t 10.000 -preset ultrafast desktop1.mp4
INFO Thread-2 com.github.kokorin.jaffree.process.ProcessHandler - Starting process: ffmpeg
INFO Thread-2 com.github.kokorin.jaffree.process.ProcessHandler - Waiting for process to finish
WARN StdErr com.github.kokorin.jaffree.process.Executor - Interrupting starter thread (Thread-2) because of exception: Stopped with exception!
WARN Thread-2 com.github.kokorin.jaffree.process.ProcessHandler - Process has been interrupted
WARN Thread-2 com.github.kokorin.jaffree.process.Executor - Interrupting ALIVE thread: StdOut
Exception in thread "Thread-2" java.lang.RuntimeException: Failed to execute, exception appeared in one of helper threads
at com.github.kokorin.jaffree.process.ProcessHandler.interactWithProcess(ProcessHandler.java:138)
at com.github.kokorin.jaffree.process.ProcessHandler.execute(ProcessHandler.java:94)
at com.github.kokorin.jaffree.ffmpeg.FFmpeg.execute(FFmpeg.java:219)
at examples.ffmpeg.Stop$2.run(Stop.java:30)
Caused by: java.lang.RuntimeException: Exception during execution
at com.github.kokorin.jaffree.process.Executor.getException(Executor.java:95)
at com.github.kokorin.jaffree.process.ProcessHandler.interactWithProcess(ProcessHandler.java:135)
... 3 more
Caused by: java.lang.RuntimeException: Stopped with exception!
at examples.ffmpeg.Stop$1.onProgress(Stop.java:19)
at com.github.kokorin.jaffree.ffmpeg.FFmpegResultReader.read(FFmpegResultReader.java:58)
at com.github.kokorin.jaffree.ffmpeg.FFmpegResultReader.read(FFmpegResultReader.java:32)
at com.github.kokorin.jaffree.process.ProcessHandler$1.run(ProcessHandler.java:166)
at com.github.kokorin.jaffree.process.Executor$1.run(Executor.java:67)
at java.base/java.lang.Thread.run(Thread.java:832)
null
------- stopWithInterruption -------
INFO Thread-3 com.github.kokorin.jaffree.process.ProcessHandler - Command constructed:
ffmpeg -f gdigrab -framerate 25 -draw_mouse 1 -i desktop -y -t 10.000 -preset ultrafast desktop2.mp4
INFO Thread-3 com.github.kokorin.jaffree.process.ProcessHandler - Starting process: ffmpeg
INFO Thread-3 com.github.kokorin.jaffree.process.ProcessHandler - Waiting for process to finish
FFmpegProgress{frame=17, fps=0.0, q=13.0, size=null, time=120, dup=11, drop=0, bitrate=34933.0, speed=0.216}
FFmpegProgress{frame=32, fps=28.0, q=13.0, size=null, time=720, dup=20, drop=0, bitrate=8737.7, speed=0.623}
FFmpegProgress{frame=47, fps=27.0, q=13.0, size=null, time=1320, dup=29, drop=0, bitrate=4766.3, speed=0.751}
FFmpegProgress{frame=60, fps=27.0, q=13.0, size=null, time=1840, dup=37, drop=0, bitrate=3419.3, speed=0.814}
FFmpegProgress{frame=75, fps=26.0, q=13.0, size=null, time=2440, dup=46, drop=0, bitrate=2578.5, speed=0.855}
FFmpegProgress{frame=88, fps=26.0, q=13.0, size=null, time=2960, dup=54, drop=0, bitrate=2125.6, speed=0.877}
FFmpegProgress{frame=100, fps=26.0, q=13.0, size=null, time=3440, dup=61, drop=0, bitrate=1829.0, speed=0.888}
FFmpegProgress{frame=113, fps=26.0, q=13.0, size=null, time=3960, dup=69, drop=0, bitrate=1588.8, speed=0.904}
WARN Thread-3 com.github.kokorin.jaffree.process.ProcessHandler - Process has been interrupted
WARN Thread-3 com.github.kokorin.jaffree.process.Executor - Interrupting ALIVE thread: StdErr
WARN Thread-3 com.github.kokorin.jaffree.process.Executor - Interrupting ALIVE thread: StdOut
Exception in thread "Thread-3" java.lang.RuntimeException: Failed to execute, was interrupted
at com.github.kokorin.jaffree.process.ProcessHandler.interactWithProcess(ProcessHandler.java:142)
at com.github.kokorin.jaffree.process.ProcessHandler.execute(ProcessHandler.java:94)
at com.github.kokorin.jaffree.ffmpeg.FFmpeg.execute(FFmpeg.java:219)
at examples.ffmpeg.Stop$3.run(Stop.java:48)
Caused by: java.lang.InterruptedException
at java.base/java.lang.ProcessImpl.waitFor(ProcessImpl.java:549)
at com.github.kokorin.jaffree.process.ProcessHandler.interactWithProcess(ProcessHandler.java:120)
... 3 more
null
------- stopForcefully -------
INFO FFmpeg-async-runner com.github.kokorin.jaffree.process.ProcessHandler - Command constructed:
ffmpeg -f gdigrab -framerate 25 -draw_mouse 1 -i desktop -y -t 10.000 -preset ultrafast desktop3.mp4
INFO FFmpeg-async-runner com.github.kokorin.jaffree.process.ProcessHandler - Starting process: ffmpeg
INFO FFmpeg-async-runner com.github.kokorin.jaffree.process.ProcessHandler - Waiting for process to finish
FFmpegProgress{frame=17, fps=0.0, q=13.0, size=null, time=120, dup=11, drop=0, bitrate=34933.0, speed=0.212}
FFmpegProgress{frame=32, fps=27.0, q=13.0, size=null, time=720, dup=20, drop=0, bitrate=8737.7, speed=0.617}
FFmpegProgress{frame=47, fps=27.0, q=13.0, size=null, time=1320, dup=29, drop=0, bitrate=4766.3, speed=0.745}
FFmpegProgress{frame=62, fps=26.0, q=13.0, size=null, time=1920, dup=38, drop=0, bitrate=3276.9, speed=0.811}
FFmpegProgress{frame=75, fps=26.0, q=13.0, size=null, time=2440, dup=46, drop=0, bitrate=2578.5, speed=0.85}
FFmpegProgress{frame=90, fps=26.0, q=13.0, size=null, time=3040, dup=55, drop=0, bitrate=2069.6, speed=0.878}
FFmpegProgress{frame=102, fps=26.0, q=13.0, size=null, time=3520, dup=62, drop=0, bitrate=1787.4, speed=0.888}
FFmpegProgress{frame=115, fps=26.0, q=13.0, size=null, time=4040, dup=70, drop=0, bitrate=1557.4, speed=0.905}
INFO FFmpeg-async-runner com.github.kokorin.jaffree.process.ProcessHandler - Process has finished with status: 1
!!======= EXCEPTION CAUGHT BY THE CALLER ======!!
java.util.concurrent.ExecutionException: java.lang.RuntimeException: Process execution has ended with non-zero status: 1
at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191)
at com.github.kokorin.jaffree.ffmpeg.FFmpegResultFuture.get(FFmpegResultFuture.java:93)
at examples.ffmpeg.Stop.stopForcefully(Stop.java:68)
at examples.ffmpeg.Stop.performStopTests(Stop.java:105)
at examples.ffmpeg.Stop.main(Stop.java:88)
Caused by: java.lang.RuntimeException: Process execution has ended with non-zero status: 1
at com.github.kokorin.jaffree.process.ProcessHandler.interactWithProcess(ProcessHandler.java:146)
at com.github.kokorin.jaffree.process.ProcessHandler.execute(ProcessHandler.java:94)
at com.github.kokorin.jaffree.ffmpeg.FFmpeg$1.call(FFmpeg.java:236)
at com.github.kokorin.jaffree.ffmpeg.FFmpeg$1.call(FFmpeg.java:233)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.lang.Thread.run(Thread.java:832)
------- stopGracefully -------
INFO FFmpeg-async-runner com.github.kokorin.jaffree.process.ProcessHandler - Command constructed:
ffmpeg -f gdigrab -framerate 25 -draw_mouse 1 -i desktop -y -t 10.000 -preset ultrafast desktop4.mp4
INFO FFmpeg-async-runner com.github.kokorin.jaffree.process.ProcessHandler - Starting process: ffmpeg
INFO FFmpeg-async-runner com.github.kokorin.jaffree.process.ProcessHandler - Waiting for process to finish
FFmpegProgress{frame=17, fps=0.0, q=13.0, size=null, time=120, dup=11, drop=0, bitrate=34933.0, speed=0.212}
FFmpegProgress{frame=32, fps=28.0, q=13.0, size=null, time=720, dup=20, drop=0, bitrate=8737.7, speed=0.619}
FFmpegProgress{frame=47, fps=27.0, q=13.0, size=null, time=1320, dup=29, drop=0, bitrate=4766.3, speed=0.747}
FFmpegProgress{frame=62, fps=26.0, q=13.0, size=null, time=1920, dup=38, drop=0, bitrate=3276.9, speed=0.812}
FFmpegProgress{frame=75, fps=26.0, q=13.0, size=null, time=2440, dup=46, drop=0, bitrate=2578.5, speed=0.846}
FFmpegProgress{frame=90, fps=26.0, q=13.0, size=null, time=3040, dup=55, drop=0, bitrate=2069.6, speed=0.873}
FFmpegProgress{frame=105, fps=26.0, q=13.0, size=null, time=3640, dup=64, drop=0, bitrate=1728.5, speed=0.89}
FFmpegProgress{frame=120, fps=26.0, q=13.0, size=null, time=4240, dup=73, drop=0, bitrate=1483.9, speed=0.904}
FFmpegProgress{frame=123, fps=25.0, q=-1.0, size=994000, time=4880, dup=75, drop=0, bitrate=1668.6, speed=0.992}
INFO FFmpeg-async-runner com.github.kokorin.jaffree.process.ProcessHandler - Process has finished with status: 0
FFmpegResult{videoSize=993000, audioSize=0, subtitleSize=0, otherStreamsSize=0, globalHeadersSize=0, muxingOverheadRatio=0.00129356}
Process finished with exit code 0
Oh, by the way, to print the FFmpegResult
, I added a toString()
method to it, as follows:
public String toString() {
return "FFmpegResult{" +
"videoSize=" + videoSize +
", audioSize=" + audioSize +
", subtitleSize=" + subtitleSize +
", otherStreamsSize=" + otherStreamsSize +
", globalHeadersSize=" + globalHeadersSize +
", muxingOverheadRatio=" + muxingOverheadRatio +
'}';
}
Maybe it could be included. It's only useful for debugging, but easy debugging is part of what makes a great lib I think.