6

I'm making a program that requires at least 24 screenshots per second to be captured. Currently with the code below I'm only getting 1 per every ~94 milliseconds, so about 10 per second.

I'd prefer not to use any 3rd party libraries because I'm trying to keep it as small as possible, but if I'd get significant performance increase I'd be willing to. I'm also trying to keep this platform independent, but again, if it would be a really significant performance increase I'd be willing to keep it limited to Windows.

edit: I've now tried it two different ways as well; using a snippet found on oracles website and the one pointed out in the comments below. All three took around the same time, 2.1-2.2 million nanoseconds which is pretty damn inefficient.

public abstract class Benchmark {

    private final int iterations;

    public Benchmark(int iterations) {
        this.iterations = iterations;
    }

    public abstract void logic();

    public void start() {
        long start = System.nanoTime();
        for (int iteration = 0; iteration < iterations; iteration++) {
            long iterationStart = System.nanoTime();
            logic();
            System.out.println("iteration: " + iteration + " took: " + (System.nanoTime() - iterationStart) + " nanoseconds.");
        }
        long total = (System.nanoTime() - start);
        System.out.println(iterations + " iterations took: " + total + " nanoseconds.  Average iteration was: " + (total / iterations));
    }
}

_

import java.awt.AWTException;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;

public class RobotBenchmark extends Benchmark {

    private final Robot robot;
    private final Rectangle screen;

    public static void main(String[] args) {
        Benchmark benchmark;
        try {
            benchmark = new RobotBenchmark(24);
            benchmark.start();
        } catch (AWTException e) {
            e.printStackTrace();
        }
    }

    public RobotBenchmark(int iterations) throws AWTException {
        super(iterations);
        robot = new Robot();
        screen = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
    }

    @Override
    public void logic() {
        robot.createScreenCapture(screen);
    }

}

_

import java.awt.AWTException;
import java.awt.GraphicsDevice;
import java.awt.HeadlessException;
import java.awt.Rectangle;

public class DirectRobotBenchmark extends Benchmark {

    private final GraphicsDevice device;
    private final Rectangle screenRectangle;
    private final DirectRobot robot;

    private int[] screen;

    public static void main(String[] args) {
        Benchmark benchmark;
        try {
            benchmark = new DirectRobotBenchmark(24);
            benchmark.start();
        } catch (HeadlessException | AWTException e) {
            e.printStackTrace();
        }
    }

    public DirectRobotBenchmark(int iterations) throws HeadlessException, AWTException {
        super(iterations);
        device = DirectRobot.getDefaultScreenDevice();
        screenRectangle = new Rectangle(1920, 1080);
        robot = new DirectRobot(device);
        screen = new int[screenRectangle.width * screenRectangle.height];
    }

    @Override
    public void logic() {
        screen = robot.getRGBPixels(screenRectangle);
    }
}

_

import java.awt.AWTException;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.peer.RobotPeer;

import sun.awt.SunToolkit;

@SuppressWarnings("restriction")
public class RobotPeerBenchmark extends Benchmark {

    private final SunToolkit toolkit;
    private final RobotPeer peer;
    private final Rectangle screenRectangle;

    private int[] screen;

    public static void main(String[] args) {
        try {
            Benchmark robotPeerBenchmark = new RobotPeerBenchmark(24);
            robotPeerBenchmark.start();
        } catch (AWTException e) {
            e.printStackTrace();
        }
    }

    public RobotPeerBenchmark(int iterations) throws AWTException {
        super(iterations);
        toolkit = (SunToolkit) Toolkit.getDefaultToolkit();
        peer = toolkit.createRobot(new Robot(), GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice());
        screenRectangle = new Rectangle(toolkit.getScreenSize());
        screen = new int[screenRectangle.width * screenRectangle.height];
    }

    @Override
    public void logic() {
        screen = peer.getRGBPixels(screenRectangle);
    }
}
eltabo
  • 3,749
  • 1
  • 21
  • 33
  • 2
    Sounds like you are trying to capture a screen video. – Jim Garrison Jul 15 '13 at 23:26
  • You could take a look at [this](http://www.rune-server.org/programming/application-development/387765-directrobot-fast-java-robot-allows-screen-recording.html), I've not used or bench marked it though – MadProgrammer Jul 15 '13 at 23:29
  • @JimGarrison Something like that. –  Jul 15 '13 at 23:42
  • 2
    @MadProgrammer I'm actually an active user on that forum and that's the first thing I compared it to haha. Sadly, though, I was getting an extremely small performance boost, like 2-4 milliseconds. –  Jul 15 '13 at 23:43
  • 1
    @MadProgrammer I did a second benchmark and can confirm that any performance increase is negligible. – Alex Barker Mar 27 '14 at 22:10

3 Answers3

3

The only way to do this will be through JNI or possibly JNA. I did some benchmarking and native screen capture API and it was able to sustain about 45 FPS vs the Robots 8 FPS. I might be starting on a JNI project to solve this issue in the near future. I will update this post with the project URL if that goes forward.

Alex Barker
  • 4,316
  • 4
  • 28
  • 47
  • Any news on this? do we have an alternative that makes use of JNI/JNA for screen capture? – Roberto Andrade Mar 03 '15 at 22:05
  • I would like to see this as well. – Doctor Parameter Jun 17 '15 at 20:05
  • Yah, I have been slacking on the project. I am working on getting the original library into maven then I will start baack up on development. – Alex Barker Jun 17 '15 at 23:28
  • 1
    Sounds good, I did a bit of research and came up with this link: http://stackoverflow.com/questions/13773870/how-to-get-over-30fps-using-java-in-a-screen-capture-program. I've also created a simple ThreadPoolExecutor implementation to help with image writing for those of us who need to write the images to files. I can provide code if you think it would help your endeavor. – Doctor Parameter Jun 18 '15 at 14:02
  • So I did some coding over the last couple of days and here are how things break down. For X11 targets I am able to get a single frame in about 10-15 miliseconds using the XShm extension. With XImage its about 5 ms per frame longer. On OSX using Core Foundation, I was able to do about 42-55 ms per frame. On my windows VM using BitBlt I was seeing about 60-75 ms per frame. The x11 numbers are barely passable at about 60 FPS. The OSX numbers equate to 20 FPS and Windows gets a whopping 15 FPS. I will post updates about native Windows tests and if I can improve performance. – Alex Barker Jun 28 '15 at 18:31
  • The FPS numbers above are at the following resolutions: Linux @ 1920x1080 , OSX @ 1440x900 and Windows @ 1920x1017 (VirtualMachine) – Alex Barker Jun 28 '15 at 18:38
  • Update #2: Its not looking good for better FPS numbers on OS X. If you have a mac and know Objective-C, please contact me. Using AVCaptureStillImageOutput may produce better reuslts, but I haven't tested it and using that API with C and JNI is going to a PITA, see http://stackoverflow.com/questions/22397904/getting-only-white-screenshot AND http://stackoverflow.com/questions/19352026/cmsamplebufferref-to-bitmap – Alex Barker Jun 28 '15 at 21:17
  • @AlexBarker Can you tell how you made the Xhsm extension and/or do you have the source to that? In 2017 this is still an issue. – RobotRock Jan 07 '18 at 22:03
  • @RobotRock The major issue last time I looked at it was Apple's slow A.F. screen capture api. It is unusably slow for anything beyond a screenshot, so I have to do some investigating into their video API and then chop up the frames... I very much dislike working with Apple's API, so if you can find an solution for their platform I would be more inclined to look at this again. – Alex Barker Jan 08 '18 at 19:20
  • @AlexBarker I'm currently looking into vlc, which seems quite promising, but you'll be hardpressed to get access to raw pixels if that's your intention. For me that's fine, as the capture is streamed to a remote client. – RobotRock Jan 08 '18 at 21:03
  • @AlexBarker See my answer on this Q. I've got it successfully working on Linux. Don't know yet about Windows or Mac, but I assume it works there fine too. – RobotRock Jan 11 '18 at 21:51
0

I've currently set up a working example of using VLCJ and then using the DirectMediaPlayer (https://github.com/caprica/vlcj/blob/master/src/test/java/uk/co/caprica/vlcj/test/direct/DirectTestPlayer.java) to get the BufferedImage.

The JFrame isn't necessary for it to work correctly.

I know this is an old question, but this is still a problem today, so I thought I'd share.

VLCJ are Java bindings for LibVLC.

Example code:

    private BufferedImage image;
    private MediaPlayerFactory factory;
    private DirectMediaPlayer mediaPlayer;

    public void start() {

            image = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration().createCompatibleImage(width, height);
            image.setAccelerationPriority(1.0f);

            String mrl = "screen://";
            String[] options = {
                    ":screen-fps=30",
                    ":live-caching=0",
                    ":screen-width=1920",
                    ":screen-height=1080",
                    ":screen-left=0",
                    ":screen-top=0"
            };
            factory = new MediaPlayerFactory();
            mediaPlayer = factory.newDirectMediaPlayer(new TestBufferFormatCallback(), new TestRenderCallback());
            mediaPlayer.playMedia(mrl, options);
    }

    // Callbacks are required.
    private final class TestRenderCallback extends RenderCallbackAdapter {

        public TestRenderCallback() {
            super(((DataBufferInt) image.getRaster().getDataBuffer()).getData());
        }

        @Override
        public void onDisplay(DirectMediaPlayer mediaPlayer, int[] data) {
            // The image data could be manipulated here...

            /* RGB to GRAYScale conversion example */
//            for(int i=0; i < data.length; i++){
//                int argb = data[i];
//                int b = (argb & 0xFF);
//                int g = ((argb >> 8 ) & 0xFF);
//                int r = ((argb >> 16 ) & 0xFF);
//                int grey = (r + g + b + g) >> 2 ; //performance optimized - not real grey!
//                data[i] = (grey << 16) + (grey << 8) + grey;
//            }
//            imagePane.repaint();
        }
    }

    private final class TestBufferFormatCallback implements BufferFormatCallback {

        @Override
        public BufferFormat getBufferFormat(int sourceWidth, int sourceHeight) {
            return new RV32BufferFormat(width, height);
        }

    }
RobotRock
  • 4,211
  • 6
  • 46
  • 86
0

Frame rates around 30FPS with pure Java can be achieved using mutiple java.awt.Robot in parallel. An example implementation can be found on GitHub

Reto
  • 1,305
  • 1
  • 18
  • 32
  • Yes thats a couple of threads - and if your machine has multiple CPUs (ok for current computers but not always guaranteed) - good idea nevertheless. But the idea is to get the original code working faster - this guy above started talking something about JNA and then changed to objective-C and other nonsense - I want to see the JNA approach!! – gpasch Apr 27 '19 at 22:43