6

下面的问题应该观察屏幕,记录一个事件(一个测量文本框变成绿色)并记录所有导致它的事件,产生一个导致它的事件的“电影”。不幸的是,需要记录整个屏幕。到目前为止,我已经完成了识别参与的部分。但是我每秒几乎没有两帧。我想要大约25 到 30 fps

我的想法是在两个单独的线程中进行写作和阅读。因为写入事件很少,并且可以在后台运行,所以录制事件会占用更多的时间并且运行得更快。不幸的是,整个事情似乎太慢了。我希望能够在事件发生前10 到 20 秒将屏幕写入磁盘。

编辑:如果可能的话,我想尽可能地保持平台独立。

编辑 2:Xuggler 似乎有一个独立于平台的 jar 文件。不幸的是,我真的不知道如何将它用于我的目的:记录导致 isarecord 被触发点的 20 秒。

这是我到目前为止所做的:

package fragrecord;

import java.awt.AWTException;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import javax.imageio.ImageIO;

public class Main {
    public static void main(String[] args) {
        //The numbers are just silly tune parameters. Refer to the API.
        //The important thing is, we are passing a bounded queue.
        ExecutorService consumer = new ThreadPoolExecutor(1,4,30,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(100));
        System.out.println("starting");
        //No need to bound the queue for this executor.
        //Use utility method instead of the complicated Constructor.
        ExecutorService producer = Executors.newSingleThreadExecutor();

        Runnable produce = new Produce(consumer);
        producer.submit(produce);  
        try {
            producer.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        consumer.shutdown();
        try {
            consumer.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
}

class Produce implements Runnable {
    private final ExecutorService consumer;

    public Produce(ExecutorService consumer) {
        this.consumer = consumer;
    }
    boolean isarecord(BufferedImage image){
        int x=10, y = 10;
        Color c = new Color(image.getRGB(x,y));
        int red = c.getRed();
        int green = c.getGreen();
        int blue = c.getBlue();
        // Determine whether to start recording
        return false;

    }


    @Override
    public void run() {

        Robot robot = null;
        try {
            robot = new Robot();
        } catch (AWTException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        //
        // Capture screen from the top left to bottom right
        //
        int i = 0;
        while(true) {

            i++;
        BufferedImage bufferedImage = robot.createScreenCapture(
                new Rectangle(new Dimension(1024, 798)));

        Runnable consume = new Consume(bufferedImage,i);
        consumer.submit(consume);
        }

    }
}

class Consume implements Runnable {
    private final BufferedImage bufferedImage;
    private final Integer picnr;
    public Consume(BufferedImage bufferedImage, Integer picnr){
        this.bufferedImage = bufferedImage;
        this.picnr = picnr;
    }

    @Override
    public void run() {
        File imageFile = new File("screenshot"+picnr+".png");
        try {
            System.out.println("screenshot"+picnr+".png");
            ImageIO.write(bufferedImage, "png", imageFile);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
4

5 回答 5

4

我尝试稍微编辑您的代码,而不是创建png文件,我尝试创建bmp文件以消除数据压缩的开销时间,但以磁盘空间为代价。

结果:我不知道如何计算 fps,但解决方案比你的更快。:-)

于 2012-09-10T07:56:53.750 回答
3

你应该衡量需要多少时间robot.createScreenCapture()。它可能需要超过 40 毫秒,这意味着使用纯 Java 无法实现您想要的。以我的经验,通话可能非常慢。

我确实设法通过一个技巧大大缩短了时间,但它仅适用于 Unix:启动 VNC 服务器(= RAM 中的桌面)。我修补了TightVNC的源代码,以使用 NIO 使用内存映射文件将图像写入磁盘。这给了我大约 10-20 fps。

下面是使用 NIO 编写图像的代码:

private File pixelFile = new File("tmp", "pixels.nio").getAbsoluteFile();
private IntBuffer intBuffer;
private FileChannel rwChannel;

private MappedByteBuffer byteBuffer;
private int[] pixels;

private void createMemoryMappedFile() {
    File dir = pixelFile.getParentFile();
    if(!dir.exists()) {
        dir.mkdirs();
    }

    try {
        rwChannel = new RandomAccessFile(pixelFile, "rw").getChannel();

        int width = ...;
        int height = ...;
        pixels = new int[width*height];

        byteBuffer = rwChannel.map(MapMode.READ_WRITE, 0, width * height * 4);
        intBuffer = byteBuffer.asIntBuffer();
    } catch(Exception e) {
        throw new RuntimeException("Error creating NIO file " + pixelFile, e);
    }
}

public void saveImage() {

     buffer.position(0);
     buffer.put(image.getRaster().getPixels(0,0,width,height,pixels));

     flushPixels();
}

private void flushPixels() {
    byteBuffer.force();
}
于 2012-09-10T08:07:49.433 回答
3

你最大的问题是你只有一个线程来实际创建图像。ThreadPoolExecutor不会按照您期望的方式创建线程。

来自 javadoc

  • 如果运行的线程少于 corePoolSize,则 Executor 总是更喜欢添加新线程而不是排队。
  • 如果 corePoolSize 或更多线程正在运行,Executor 总是更喜欢排队请求而不是添加新线程。
  • 如果请求无法排队,则会创建一个新线程,除非这将超过 maximumPoolSize,在这种情况下,该任务将被拒绝。

因此,除非队列已满,否则它将只使用一个线程。此时,您在内存中有 100 个屏幕截图,这些屏幕截图正在向 GC 添加工作。如果我将核心线程设置为 4(我的笔记本电脑上有 4 个核心)并将内存增加到 1 GB,我设法捕获 20 FPS 左右。

如果您对磁盘的输出受到限制,您可以将最后 400 个写入的图像存储为队列中的字节数组,并且仅在按钮变为“绿色”时将它们写入磁盘。然而在我的测试中,这些图像会占用超过 100MB 的内存,所以再次确保你有足够的内存。

于 2012-09-10T08:08:08.713 回答
2

由于使用执行器的各种原因,重写到 nio 和类似的事情不会有帮助。

这里有一堆你应该考虑的事情:

  1. 图像捕获在 java 中很慢,如果没有 JNI 依赖项,您将无能为力
  2. 使用 jpeg 而不是 png,更快
  3. 使用 ImageIO 的任何图像压缩都很慢。您可以使用老式的专有 JpegEncoder 类(在 com.sun 包中)或 TurboJPEG java 库(也可以使用 JNI),但恕我直言,这不是瓶颈
  4. 磁盘 i/o 绝对不是问题(您正在写入 <5mb/s,所以不用担心)
  5. 并行编写许多图像实际上会减慢您的应用程序,而不是加快速度(除非您有 ssd)
  6. 考虑并行化捕获/分析线程(例如,仅每 20 帧进行一次分析)(*)
  7. 我敢打赌,在您可以充分优化 Java 应用程序以使其以 25fps 运行(使用 100% cpu xD)之前,您会使用本机语言为每个平台完成两次编写此应用程序
  8. 认真考虑混合解决方案;例如,使用可用工具将所有内容记录到压缩电影中,然后再进行分析(**)

一言以蔽之:java 对你想做的事情很糟糕,真的很糟糕。

尽管如此,我已经编写了我自己版本的这个工具。 http://pastebin.com/5h285fQw

它所做的一件事是允许在鼠标之后记录一个较小的矩形。在 500x500 时,它可以让我轻松达到 25fps,并在后台写入图像(图像压缩 + 写入对我来说需要 5-10 毫秒,因此它的写入速度比记录速度快得多)


(*)您没有谈论如何分析图像,但这似乎是您的性能问题的主要来源。一些想法:

  • 只看第 N 帧
  • 仅捕获屏幕的一个子部分并随着时间的推移移动该子部分(稍后重新组合图像;这会导致严重的撕裂,但可能对您的目的无关紧要)
  • 捕获本身应该“不太慢”(全屏可能为 10-20fps);使用串行捕获但并行化分析

(**) 在 macosx 上,您可以使用内置的 quicktime x 非常有效地记录到硬盘;在 Windows 上,我听说 playclaw (http://www.playclaw.com/) 非常好,也许物有所值(想想你浪费时间优化 Java 野兽会做什么:))

于 2012-09-18T23:33:52.810 回答
1

离题,但看看Xuggler。如果您想使用 Java 创建视频,这将很有用。

编辑:此外,如果您不将每张图片转储到磁盘,而是将它们附加到字节数组,并且很少将其转储到磁盘上,则可以优化您的图像消费者代码。

编辑 2:有“无安装”版本的库和 maven 依赖项(带有预编译平台特定库的 jar):blog.xuggle.com/2012/04/03/death-to-installersxuggle。 com/xuggler/下载

于 2012-09-10T07:26:53.837 回答