How to get over 30FPS using Java in a Screen Capture Program? - java

I'm currently using the Robot classes in Java to record the screen. However, it does not achieve the minimum of 30 frames per second. I'm not re-creating objects, and am being as efficient as I can, but I only average around 15 frames per second. Robot is simply not cutting it.
What can I use to capture the screen? I've tried Xuggle, but I can't seem to get that to capture fast enough either.

For operating systems following the X11 standard (Linux, FreeBSD, Solaris, etc.), we can do it this way via JavaCV and FFmpeg:
import com.googlecode.javacv.*;
public class ScreenGrabber {
public static void main(String[] args) throws Exception {
int x = 0, y = 0, w = 1024, h = 768; // specify the region of screen to grab
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(":0.0+" + x + "," + y);
grabber.setFormat("x11grab");
grabber.setImageWidth(w);
grabber.setImageHeight(h);
grabber.start();
CanvasFrame frame = new CanvasFrame("Screen Capture");
while (frame.isVisible()) {
frame.showImage(grabber.grab());
}
frame.dispose();
grabber.stop();
}
}
I don't know about Windows or Mac OS X, but I suspect we would need to access native APIs directly. Nevertheless, JavaCPP could help with that.

Build on #Samuel's answer, according to the official ffmpeg documentation you should be able to keep it pretty cross platform if you make the file parameter passed to the FFmpegFrameGrabber (which is really an input parameter that gets passed down as the -i option to ffmpeg) adhere to the different formats each device expects.
ie:
for Windows: dshow expects -i video="screen-capture-recorder"
for OSX: avfoundation expects -i "<screen device index>:"
and for Linux: x11grab expects -i :<display id>+<x>,<y>.
So just passing those values (arguments to -i) to the constructor and setting the format (via setFormat) accordingly should do the trick:
Examples:
for Windows:
new FFmpegFrameGrabber("video=\"screen-capture-recorder\"")
.setFormat("dshow");
for OSX:
new FFmpegFrameGrabber("\"<screen device index>:\"")
.setFormat("avfoundation");
for Linux:
new FFmpegFrameGrabber(":<display id>+<x>,<y>")
.setFormat("x11grab");
PS: Haven't tested this fully so not sure if the quotes are actually necessary.

Related

Java 8 on Big Sur reports os.name as "Mac OS X" and os.version as "10.16"

Is it just my setup or is anyone else having this problem?
Using AdoptOpenJDK 1.8.0_275 installed at:
/Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home/jre
API docs of System.getProperties() do not specify any details.
Can confirm this is still happening on adoptopenjdk14, as well as openjdk early access build for j16.
You can file a bug if you want, but I bet it'll be denied. At this point, the name Mac OS X is not so much 'the name of the OS' as a 'globally agreed upon keyword identifying that unix-based mac operating system', where I mean globally literally (as in, 'around the planet', not 'across your source base/VM'). Changing it would just break stuff needlessly. The same applies, to a lesser degree, to version 10.16: The thing before the dot is not so much 'this is how the OS identifies itself' and more a 'globally agreed upon versioning scheme for Mac OS, identifying a wide and ill defined set of capabilities'.
There is no meaningful difference between the transition between big sur and catalina, other than the fact that apple made a marketing decision. If you want to point at an OS transition that might warrant the entirely nebulous choice to consider it a 'major change', surely it was the one to catalina, as that made by far the largest changes (including removing support for 32-bit entirely) in the last bunch of releases.
This leaves you with the challenge of: Okay, great, I can use System.getProperty("os.name") to get the globally agreed upon keyword that means unix-like Mac OS, and os.version for a string I can break into bits to figure out some nebulous batch of capabilities, but what if I need the actual name of the OS to e.g. show to a user?
Then you have three major options:
The easy one is to just write mapping code. Acknowledge that os.name and os.version give you (rather arguably) useful intent and not so much official names, and therefore, write some mappings. These would map name/version pairs to rendering strings, falling back to just printing the name string and the version string, concatenated, verbatim. You could add a mapping: Mac OS X/10.16 → Mac OS Big Sur in this table.
The hard way: Figure out you're on a mac (which is now easier; os.name returns Mac OS X, or just check for the existence: Files.isExecutable(Paths.get("/usr/bin/sw_vers"))), and then use ProcessBuilder to execute /usr/bin/sw_vers, picking up all output into a big string, and then parse it. Its output looks like:
ProductName: macOS
ProductVersion: 11.1
BuildVersion: 20C69
which, crucially, doesn't even include the words Big Sur, but does give you 11.1. I don't know how to run a command line tool that actually gives you Big Sur. Maybe system_profiler, but note that this takes many minutes to run, I really doubt you want to run that.
NB: you can also run .command("/usr/bin/sw_vers", "-productVersion") which gives you just 11.1, this may be a lot simpler to parse. -productName also works, gives you just macOS.
If you need this information to scan for OS capabilities, then stop doing this. It doesn't work with browsers, and it's not a good plan for OS releases either. What capability are you scanning for? Imagine, for example, if it is 'Can I run /usr/bin/sw_vers to figure stuff out', as a hypothetical example. The right strategy is NOT to check os.name/os.version, conclude that the command must exist, and then run it, failing catastrophically if it is not there. The right move is to check if /usr/bin/sw_vers exists, and then execute it, falling back to some non-mac based solution (perhaps /usr/bin/uname) in other cases. Scan for the capability, don't scan for the OS/version.
Java code to call native tool sw_vers
Regarding Option # 2 in the Answer by rzwitserloot, here is a complete code example to run from Java a command-line tool sw_vers that describes the version of macOS software running on the host computer.
If on the command-line (console) such as in Terminal.app, you run:
sw_vers
…in Big Sur on an Intel Mac we get:
ProductName: macOS
ProductVersion: 11.2
BuildVersion: 20D64
We only need the middle piece. So running:
sw_vers -productVersion
…shows simply 11.2, the value we need for your purpose.
Here is complete example app with a method to return this string into Java.
ProcessBuilder class creates operating system processes. Each new process is represented by the Process class.
We use try-with-resources syntax to automatically close the InputStream and Scanner objects.
Once you have the 11.2 string in hand, split on the FULL STOP, pull the first number 11, and you know you are running on Big Sur.
package org.example;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
/**
* Example code showing how to get current version of macOS from Java
* by running a native command-line tool `sw_vers`.
*/
public class App
{
public static void main ( String[] args )
{
App app = new App();
app.demo();
}
private void demo ( )
{
String version = this.getMacOsVersionNumber();
System.out.println( "version = " + version );
}
public String getMacOsVersionNumber ( )
{
String result = "";
List < String > command = List.of( "sw_vers" , " -productVersion" );
try (
InputStream inputStream = new ProcessBuilder( command ).start().getInputStream() ;
Scanner s = new Scanner( inputStream ).useDelimiter( "\\A" ) ;
)
{
result = s.hasNext() ? s.next() : "";
}
catch ( IOException e )
{
e.printStackTrace();
}
return Objects.requireNonNull( result );
}
}

Setting milliseconds of System time with java

Would it be possible to set system time with milliseconds component on Windows OS using Java?
I am trying to synchronize clocks between couple of computers but the OS API seems to offer only set time in format: HH:MM:SS.
This is what i tried:
public static void main(String[] args) throws InterruptedException, IOException {
String time="10:00:20"; // this works
String timeWithMiliseconds = "10:00:20.100"; // this doesn't change time at all
Runtime rt = Runtime.getRuntime();
rt.exec("cmd /C time " + time);
}
I am wondering how do NTP clients work if it's not possible to set milliseconds component ?
One way to deal with this issue could be to calculate time in milliseconds when server should reach next second, and sleep for that time. Is there a better, more direct way to achieve this ?
As mentioned by immibis you could use the Windows function SetSystemTime to set the time.
Find below a snippet which call the Windows function using JNA 4.2
Kernel32 kernel = Kernel32.INSTANCE;
WinBase.SYSTEMTIME newTime = new WinBase.SYSTEMTIME();
newTime.wYear = 2015;
newTime.wMonth = 11;
newTime.wDay = 10;
newTime.wHour = 12;
newTime.wMinute = 0;
newTime.wSecond = 0;
newTime.wMilliseconds = 0;
kernel.SetSystemTime(newTime);
For further information have a look into the sources of JNA and on those links
SetSystemTime Windows function and
SYSTEMTIME structure.
An introduction to JNA from 2009. Simplify Native Code Access with JNA

Run GraphicsMagick compare command using im4java + gm4java

I am trying to do a GraphicsMagick compare using im4java and gm4java. The GraphicsMagick command I'm using is like this:
gm compare -maximum-error 0 -metric MAE -highlight-style xor -hilight-color red -file C:/output/diffFile.pdf C:/input/file1.pdf C:/input/file2.pdf
I'm trying to translate that into Java. I know that im4java was originally built for ImageMagick and their commands may differ. Is it possible to run the above compare using im4java plus gm4java?
I've tried this:
SimpleGMService service = new SimpleGMService();
service.setGMPath("C:/path/to/graphicsMagick/gm.exe");
try
{
GMConnection connection = service.getConnection();
try {
GMBatchCommand command = new GMBatchCommand(service, "compare");
// create the operation, add images and operators/options
IMOperation op = new IMOperation();
op.metric("MAE");
op.addRawArgs("-file C:/output/diffFile.pdf");
op.addImage();
op.addImage();
ArrayListOutputConsumer output = new ArrayListOutputConsumer();
command.setOutputConsumer(output);
//debug
command.createScript("C:/output/myscript.bat",op);
command.run(op, "C:/input/file1.pdf", "C:/input/file2.pdf");
....
The above gives me the error:
org.im4java.core.CommandException: compare: Unrecognized option (-file C:/output/diffFile.pdf)
You can attain this by using im4java alone or im4java+gm4java. What gm4java gives you is the performance when you need to process a large number of images.
The problem you are getting is due to the improper use of the addRawArgs() method. Each argument you have in your command line needs to be added as individual arguments instead of all together in one string.
Try:
op.addRawArgs("-file", "C:/output/diffFile.pdf");

close cmd + frame when click on the close button

hello i have a code that when runned opens cmd and then it opens the frame. i want the cmd to be closed as soon as the frame is opened or the cmd should be closed at the same moment as the user closes the frame. this is the code when i close my frame.
frame = new JFrame("BrainSla");
frame.setLayout(new BorderLayout());
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
here is the main code:
public static void main(String args[]) {
try {
System.out.println("BrainSla - By Jannes Braet, Steven Brain, Wout Slabbinck.");
nodeID = 10;
portOff = 0;
setHighMem();
isMembers = true;
signlink.storeid = 32;
signlink.startpriv(InetAddress.getLocalHost());
new Jframe(args);
//instance = new client();
//instance.createClientFrame(503, 765);
} catch(Exception e) {
e.printStackTrace();
}
}
could someone tell me how i could do something like that ?
change frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); to
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
This will exit the application as soon as you close the frame
If by 'cmd' you are referring to the 'command line' or CLI. Options:
Launch it from a bat file (or similar per OS) using javaw instead of java
Low learning curve.
Not very professional look.
Make it a runnable Jar (double click to open)
Medium learning curve.
Medium professional look.
Launch it using JWS
High learning curve.
Very professional look.
If you run from a command line closing the command line window will close your application prematurely. Not sure of any way to do it on Windows but on Linux you can background the process and do it using the command:
nohup java -jar myprogram.jar &
If you start the process from with in your Java application (ex. by calling Runtime.exec() or ProcessBuilder.start()) then you have a valid Process reference to it, and you can invoke the destroy() method in Process class to kill that particular process.
But be aware that if the process that you invoke creates new sub-processes, those may not be terminated (see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4770092).
On the other hand, if you want to kill external processes (which you did not spawn from your Java app), then one thing you can do is to call O/S utilities which allow you to do that. For example, you can try a Runtime.exec() on kill command under Unix / Linux and check for return values to ensure that the application was killed or not (0 means success, -1 means error). But that of course will make your application platform dependent.

Java DTrace bridge on OS X

I am trying to grab filesystem events on OS / Kernel level on OS X.
There are 2 requirements i have to follow. The first one is to do this in java as the whole project im developing for is written in java. The second one is that i have to find out when a document is opened.
For Linux I used inotify-java, but I can't find a good equivalent on OS X. Also the JNA doesn't provide a helpful binding. Currently I'm avoiding catching events by frequently calling the lsof program. This, however, is a bad solution.
Thanks for the help.
You can use dtrace on OSX, but since it needs root privileges it's not something you'd want to put into a runtime of a system.
In any case, you won't be able to do this in pure Java (any Java API would be a wrapper around some lower level C introspection, and if you're doing it kernel-wide, would need to be done as root).
If you just want to track when your program is opening files (as opposed to other files on the same system) then you can install your own Security Manager and implement the checkRead() family of methods, which should give you an idea of when accesses are happening.
import java.io.*;
public class Demo {
public static void main(String args[]) throws Exception {
System.setSecurityManager(new Sniffer());
File f = new File("/tmp/file");
new FileInputStream(f);
}
}
class Sniffer extends SecurityManager {
public void checkRead(String name) {
System.out.println("Opening " + name);
}
}

Categories

Resources