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

时间:2012-09-10 07:02:04

标签: java multithreading video-capture

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

我的想法是用两个单独的线程进行写作和阅读。由于写入事件很少且可以在后台运行,因此录制事件可能会占用更多时间并且运行速度更快。不幸的是,整个事情似乎太慢了。我希望能够在事件发生前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();
        }
    }
}

3 个答案:

答案 0 :(得分:3)

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

来自javadoc

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

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

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

答案 1 :(得分:2)

由于各种原因使用执行程序,重写到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. 也认真考虑混合解决方案;例如使用可用工具将所有记录到压缩影片中,然后再进行分析(**)
  9. 用一句话来说:java很糟糕你想做什么。

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

    它做的一件事是允许在鼠标后面记录一个较小的矩形。 在500x500下,它可以轻松地将我的图像写入背景中,速度达到25fps(图像压缩+写入对我来说需要5-10ms,因此写入速度比记录快得多)


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

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

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

答案 2 :(得分:1)

偏离主题,但请看Xuggler。如果你想用Java创建视频,这将是有用的。

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

编辑2:“no-install”版本的库和maven依赖项(带有预编译平台特定库的jar):blog.xuggle.com/2012/04/03/death-to-installers和{{3} }