单独线程中的屏幕捕获使 Java 应用程序变慢/无响应

Posted

技术标签:

【中文标题】单独线程中的屏幕捕获使 Java 应用程序变慢/无响应【英文标题】:Screen Capture in separate thread making Java application slow/unresponsive 【发布时间】:2013-11-13 09:11:51 【问题描述】:

我正在开发一个应用程序,该应用程序在他/她执行某些活动时记录用户屏幕、网络摄像头和麦克风。它将用于研究目的。该应用程序已在 Windows 上成功测试,但在 Mac OS X(带有Java 7.0.45 的 Maverick)上,该应用程序在开始录制时变得缓慢无响应

这就是为什么我觉得这很难理解:

录制是在单独的线程中完成的,那么它如何影响另一个线程的响应?尤其是在每次运行之后,Thread.yield()Thread.sleep(...) 都会被调用。 日志显示,在尝试以15 FPS 录制时,生成的帧速率为2 FPS。所以看起来捕获单帧的代码可能太慢了。但为什么它在 Windows 上运行良好?

请注意:该应用程序已在 Windows 上被大量用户成功测试,但我只需要在一台 Mac 上对其进行测试。然而,那个刚刚被格式化并得到了 OS X Maverick、Java(和 Netbeans)的全新安装。

您将在下面找到记录屏幕并使用 Xuggler 将其写入视频的代码。录制网络摄像头的代码是相似的,我怀疑录制音频与它有什么关系。我的问题是:

应用程序无响应的原因可能是什么?

如何使代码更高效,从而提高 FPS?

IMediaWriter writer = ToolFactory.makeWriter(file.getAbsolutePath());
Dimension size = Globals.sessionFrame.getBounds().getSize();
Rectangle screenRect;
BufferedImage capture;
BufferedImage mousePointImg;


writer.addVideoStream(0, 0, ICodec.ID.CODEC_ID_H264, size.width, size.height);

int i = 0;

while (stop == false) 

    // Get mouse cursor to draw over screen image.
    PointerInfo mousePointer = MouseInfo.getPointerInfo();
    Point mousePoint = mousePointer.getLocation();
    Point screenPoint = new Point((int) (mousePoint.getX() - 
        Globals.sessionFrame.getBounds().getX()), (int) (mousePoint.getY() - 
        Globals.sessionFrame.getBounds().getY()));

    // Get the screen image.
    try 
        screenRect = new Rectangle(Globals.sessionFrame.getBounds());
        capture = new Robot().createScreenCapture(screenRect);
     catch ( ... )  ... 

    // Convert and resize the screen image.
    BufferedImage image = ConverterFactory.convertToType(capture, 
        BufferedImage.TYPE_3BYTE_BGR);
    IConverter converter = ConverterFactory.createConverter(image, 
        IPixelFormat.Type.YUV420P);

    // Draw the mouse cursor if necessary.
    if (mouseWithinScreen()) 
        Graphics g = image.getGraphics();
        g.drawImage(mousePointImg, (int) screenPoint.getX(), 
            (int) screenPoint.getY(), null);
    

    // Prepare the frame.
    IVideoPicture frame = converter.toPicture(image, (System.currentTimeMillis() - 
        startTimeMillis()) * 1000);
    frame.setKeyFrame(i % (getDesiredFPS() * getDesiredKeyframeSec()) == 0);

    // Write to the video
    writer.encodeVideo(0, frame);

    // Delay the next capture if we are at the desired FPS.
    try 
        if (atDesiredFPS()) 
            Thread.yield();
         else 
            Thread.sleep(1000 / getDesiredFPS());
        
     catch ( ... )  ... 

    i++;


writer.close();

【问题讨论】:

你有足够多的内核用于线程,对吧?发生这种情况时 CPU 是否忙? @maaartinus CPU 似乎忙于处理应用程序,是的。大约有 8 个线程,因此通常线程数比内核数多。 使用@TwoThe 答案中的想法,您可以轻松禁用部分处理并查看发生了什么。 【参考方案1】:

我可以在您的代码中看到几个架构问题:

首先,如果您想以固定速率执行某项操作,请使用ScheduledThreadPoolExecutor.scheduleAtFixedRate(...) 函数。这将使您的整个延迟代码部分过时,并确保某些操作系统时序问题不会干扰您的日程安排。

然后,为了让事情变得更快,您需要将代码分开一点。据我所知,您有 3 个任务:捕获、鼠标绘图/转换和流写入。如果将捕获部分放在一个调度的 Runnable 中,转换为多并行执行作为 Callables 到一个 Executor 中,然后在第三个线程中从结果列表中取出结果并将其写入流中,您可以充分利用多核心。

伪代码:

全局声明(或将它们交给各个类):

final static Executor converterExecutor = Executors.newFixedThreadPoolExecutor(Runtime.getRuntime().availableProcessors());
final static LinkedBlockingQueue<Future<IVideoPicture>> imageQueue = new LinkedBlockingQueue<>();
// ...

Capture Runnable(以固定速率计划):

capture = captureScreen();
final Converter converter = new Converter(capture);
final Future<IVideoPicture> conversionResult = converterExecutor.submit(converter);
imageQueue.offer(conversionResult); // returns false if queue is full

转换调用:

class Converter implements Callable<IVideoPicture> 
  // ... variables and constructor

  public IVideoPicture call() 
    return convert(this.image);
  

Writer Runnable:

IVideoPicture frame;
while (this.done == false) 
  frame = imageQueue.get();
  writer.encodeVideo(0, frame);

如果 CPU 太慢,您可以通过限制此队列的大小来确保 imageQueue 不会溢出要渲染的图像,请参阅constructor of LinkedBlockingQueue。

【讨论】:

这些是一些正确的指针。非常感谢。我会直接进入这个。 如果帧速率设置为高于 CPU 的处理能力,难道 scheduleAtFixedRate 会试图跟上不可能的速度,从而使应用程序变慢且无响应吗?

以上是关于单独线程中的屏幕捕获使 Java 应用程序变慢/无响应的主要内容,如果未能解决你的问题,请参考以下文章

从另一个线程捕获异常

Objective C 中的部分屏幕截图

JAVA 线程中的异常捕获

Form.Owner 从 .NET 3.5 中的单独线程设置

在 iOS 中检测不活动(无用户交互)以显示单独的屏幕,如覆盖

html5 应用程序中的 gif 会使应用程序变慢?