繁体   English   中英

从屏幕捕获并保存到磁盘多线程

[英]Capturing from the screen and saving to disk multithreaded

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

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

编辑:如果可能的话,我希望尽可能保持与平台无关。

编辑2:Xuggler似乎有一个独立于平台的jar文件。 不幸的是,我并没有真正了解我将如何能够将它用于我的目的:记录20秒,直到触发isarecord。

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

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();
        }
    }
}

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

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

您应该测量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();
}

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

来自javadoc

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

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

如果您的磁盘输出是限制性的,您可以将最后400个写入的图像作为字节数组存储在队列中,并在按钮变为“绿色”时将它们写入磁盘。 但是在我的测试中,这些图像需要超过100MB的RAM,所以再次确保你有足够的内存。

由于使用执行程序的各种原因,重写到nio和类似的东西都无济于事。

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

  1. 图像捕获在java中是缓慢的,没有JNI依赖项,你无能为力
  2. 使用jpeg代替png,速度更快
  3. 使用ImageIO,任何图像压缩都是SLOW。 你可以使用老式的,专有的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-10ms,因此写入比记录快得多)


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

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

(**)在macosx上你可以使用内置的quicktime x来非常有效地记录到hdd; 在Windows上,我听说playclaw(http://www.playclaw.com/)非常好,也许值得花钱(想想你在浪费的时候浪费了优化java野兽:))

关闭主题,但看看Xuggler 如果您想使用Java创建视频,那将非常有用。

编辑:此外,如果您不将每张图片转储到磁盘,但可以将它们附加到字节数组,并将它们很少转储到磁盘上,您可以优化图像使用者代码。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM