3

I'm implementing video capture in a Java program by creating BufferedImages at a user-defined interval (for my testing, 100ms), and then using those images to make a movie file. The JFrame that I am trying to record from includes a dashboard-like interface contained by a JLayeredPane. The JFrame also has two Canvas3Ds. I'm telling each of these 3 things to render or paint into their own LinkedBlockingDeque<BufferedImage>, and I combine them later. The dashboard is set to only render every dashboardFrameRepaintFrequency frames.

Thread captureThread = new Thread(new Runnable() {
    public void run() {
        final Object LOCK = new Object();
            final Thread captureDashboard = new Thread(new Runnable() {
                public void run() {
                    while (m_isCapturingMovie) {
                        synchronized (LOCK) {
                            try {
                                LOCK.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            } finally {
                                System.err.println("Calling getDashboardImage");
                                m_unsavedDash.add(getDashboardImage(m_dashboard.getWidth(), m_dashboard.getHeight(), BufferedImage.TYPE_INT_ARGB));
                                System.err.println("captureDashboard returned from calling m_unsavedDash.add...");
                            }
                        }
                    }
                }
            });
            captureDashboard.start();

            while (m_isCapturingMovie) {
                startTime = System.currentTimeMillis();
                captureCanvases();
                if (++frameCount > dashboardFrameRepaintFrequency) {
                    frameCount = 0;
                    synchronized (LOCK) {
                        LOCK.notify();
                    }
            }
            endTime = System.currentTimeMillis();
            millisToSleep = captureDelayInMillis - (endTime - startTime);
            if (millisToSleep > 0) {
                try {
                    Thread.sleep(millisToSleep);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        synchronized (captureDashboard) {
        captureDashboard.notify();
        }
    }
});

I've found that after 15-20 notify()s, the program locks up - it stops recording the Canvases, stops responding to keyboard input.. I can still change which window has focus, and the buttons (such as the X button to close a window) still change visual state from mouse rollover or clicking, but they don't execute their commands.

From the console output, it seems that the captureDashboard thread, after those 15-20 iterations, does not return from the getDashboardImage method:

private BufferedImage getDashboardImage(int width, int height, int type) {
    BufferedImage dashImg = new BufferedImage(m_dashboard.getWidth(), m_dashboard.getHeight(), type);
    Graphics2D g = dashImg.createGraphics();
    m_dashboard.paintAll(g);
    g.dispose();

    return getScaledImage(width, height, dashImg);
}

private BufferedImage getScaledImage(int width, int height, BufferedImage original) {
    BufferedImage scaled = new BufferedImage(width, height, original.getType());

    AffineTransform at = new AffineTransform();
    at.scale((double) scaled.getWidth() / original.getWidth(), (double) scaled.getHeight() / original.getHeight());
    AffineTransformOp scaleOp;
    if (at.getScaleX() + at.getScaleY() > 2.0) {
        scaleOp = new AffineTransformOp(at, AffineTransformOp.TYPE_BICUBIC); // Better quality for enlargement
    } else {
        scaleOp = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR); // Better quality for ensmallment
    }

    scaled = scaleOp.filter(original, scaled);
    original.flush();
    return scaled;
}

Any ideas? I've been working at this for a few days and I'm stumped.

4

1 回答 1

1

问题是我需要paintAll从 AWT 调度线程调用。

所以而不是:

m_dashboard.paintAll(g);

我需要:

final Graphics2D g = dashImg.createGraphics();

EventQueue.invokeLater(new Runnable() {
    public void run () {
        m_dashboard.paintAll(g);
    }
});

然而,这导致我的程序“超前”并返回BufferedImage它被绘制到之前的状态,但前提是程序负载很重。为了解决这个问题,我刚刚添加了以下内容:

final Graphics2D g = dashImg.createGraphics();
final SyncObj LOCK = new SyncObj();

EventQueue.invokeLater(new Runnable() {
    public void run () {
        m_dashboard.paintAll(g);
        LOCK.doNotify();
    }
});

LOCK.doWait();
g.dispose();

SyncObj 只是一个简单的地方:

class SyncObj {
    private boolean condition = false;
    private Object obj = new Object();

    public void doWait() {
        synchronized (obj) {
            while (!condition) {
                try {
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            condition = false;
        }
    }

    public void doNotify() {
        synchronized (obj) {
            condition = true;
            obj.notify();
        }
    }
}
于 2013-07-22T15:03:38.490 回答