使用Java和awt.Robot时如何提高屏幕截图速度

12

编辑:如果有其他建议可以提高屏幕捕捉性能,请随时分享,可能会完全解决我的问题!

大家好,开发者们:

我正在为自己编写一些基本的屏幕捕捉软件。截至目前,我已经拥有了一些概念验证/调试代码,它使用java.awt.Robot来捕捉屏幕作为BufferedImage。然后,我会对此进行捕捉指定时间,并在之后将所有图片转储到磁盘中。根据我的测试,我每秒得到约17帧。

试用#1

持续时间:15秒 捕获图像:255个

试用#2

持续时间:15秒 捕获图像:229个

显然,这对于真正的屏幕捕获应用程序来说还远远不够好。特别是因为这些捕获只是我在IDE中选择文本而已,没有任何涉及图形方面的内容。

我现在有两个类,一个是Main类,另一个是"Monitor"类。Monitor类包含捕捉屏幕的方法。我的Main类有一个基于时间的循环,调用Monitor类并将其返回的BufferedImage存储到一个ArrayList中。 如果我修改我的主类以生成多个线程,每个线程执行该循环并收集关于捕获图像时间的信息,那么我能提高性能吗?我的想法是使用一个共享数据结构,在插入它们时自动根据捕获时间对帧进行排序,而不是将连续的图像插入到ArrayList中的单个循环。

代码:

监视器

public class Monitor {

/**
 * Returns a BufferedImage
 * @return
 */
public BufferedImage captureScreen() {
    Rectangle screenRect = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
    BufferedImage capture = null;

    try {
        capture = new Robot().createScreenCapture(screenRect);
    } catch (AWTException e) {
        e.printStackTrace();
    }

    return capture;
}
}

主要

public class Main {


public static void main(String[] args) throws InterruptedException {
    String outputLocation = "C:\\Users\\ewillis\\Pictures\\screenstreamer\\";
    String namingScheme = "image";
    String mediaFormat = "jpeg";
    DiscreteOutput output = DiscreteOutputFactory.createOutputObject(outputLocation, namingScheme, mediaFormat);

    ArrayList<BufferedImage> images = new ArrayList<BufferedImage>();
    Monitor m1 = new Monitor();

    long startTimeMillis = System.currentTimeMillis();
    long recordTimeMillis = 15000;

    while( (System.currentTimeMillis() - startTimeMillis) <= recordTimeMillis ) {
        images.add( m1.captureScreen() );
    }

    output.saveImages(images);

}
}

你一直在创建新的矩形和新的机器人。试着将它们设为实例的最终值,只使用字段而不是创建新的实例。这应该会有所帮助。 - Obicere
@Obicere 我已经这样做了,但性能仍然类似。但是你知道或认为多线程的想法值得追求吗? - EthanLWillis
1
我对你所做的事情不是很熟悉,但似乎如果你创建了一个机器人/核心,你就可以在主核心忙碌时循环遍历每个机器人并在不同的核心上拍摄屏幕截图。 - Colton
1
如果你要进行多线程处理,或许可以使用队列,并在添加图像时对其进行同步。这样,你可能可以避免在输入图像时进行排序。另外,你也可以在所有图像完成后再进行排序。如果你关心的是FPS,那么在添加图像时不要执行任何排序计算。 - hankd
2
它不是使用awt.Robot,但速度更快 https://dev59.com/tE7Sa4cB1Zd3GeqP0Ahg#4843247。 - André
显示剩余2条评论
2个回答

4
重新使用屏幕矩形和机器人类实例会节省一些开销。真正的瓶颈是将所有BufferedImage存储到数组列表中。
我建议首先对你的robot.createScreenCapture(screenRect)调用进行基准测试,不进行任何IO操作(不保存或存储缓冲图像)。这将为您提供机器人类的理想吞吐量。
long frameCount = 0;
while( (System.currentTimeMillis() - startTimeMillis) <= recordTimeMillis ) {
    image = m1.captureScreen();
    if(image !== null) {
        frameCount++;
    }
    try {
        Thread.yield();
    } catch (Exception ex) {
    }
}

如果captureScreen可以达到您想要的FPS,则无需多线程机器人实例。
与其拥有一个缓冲图像数组列表,不如拥有一个来自AsynchronousFileChannel.write的Future的数组列表。
  • 捕获循环
    • 获取BufferedImage
    • 将BufferedImage转换为包含JPEG数据的字节数组
    • 为输出文件创建异步通道
    • 启动写入并将即时返回值(即未来)添加到您的ArrayList中
  • 等待循环
    • 遍历您的未来ArrayList,并确保它们都已完成

好的回答。我认为需要对捕获循环进行一些修改,将字节数组转换为第一个捕获图像的增量编码字节数组。然后,您应该拥有一个稀疏数据数组,可以更轻松地进行压缩和存储。 - EthanLWillis
@EthanWillis,你知道有哪些推荐的Java delta编码字节数组实现吗? - eSniff

2

我猜这里的问题在于内存使用过度。您在测试中捕获了大约250个屏幕截图。根据屏幕分辨率,这可能是:

1280x800 : 250 * 1280*800  * 3/1024/1024 ==  732 MB data
1920x1080: 250 * 1920*1080 * 3/1024/1024 == 1483 MB data

尝试在不将所有这些图像保存在内存中的情况下进行捕捉。

正如@Obicere所说,保持Robot实例活动是一个好主意。


是的。从我所阅读的所有内容来看,需要做的就是捕获初始图像。然后获取该图像及所有后续图像的byte[]数据。对于所有后续图像,将它们的byte[]数据转换为第一个byte[]的增量编码版本,从而大大降低空间要求。然后,如果需要写入磁盘,速度也会更快。 - EthanLWillis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接