Reputation: 61
I'm implementing video capture in a Java program by creating BufferedImage
s 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 Canvas3D
s. 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.
Upvotes: 4
Views: 102
Reputation: 61
The problem was that I needed to call paintAll
from the AWT dispatch thread.
So instead of:
m_dashboard.paintAll(g);
I needed to have:
final Graphics2D g = dashImg.createGraphics();
EventQueue.invokeLater(new Runnable() {
public void run () {
m_dashboard.paintAll(g);
}
});
This, however, caused my program to "get ahead of itself" and return the BufferedImage
before it had been painted to, but ONLY if the program was under heavy load. To account for this, I just added the following:
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();
Where SyncObj is just a simple:
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();
}
}
}
Upvotes: 2