- improve multithreading in VideoRecorderAppState

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@8713 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
nor..67 13 years ago
parent 2253690024
commit 04e348f0fb
  1. 26
      engine/src/desktop/com/jme3/app/state/MjpegFileWriter.java
  2. 135
      engine/src/desktop/com/jme3/app/state/VideoRecorderAppState.java

@ -18,6 +18,7 @@ import javax.imageio.ImageIO;
* @author monceaux, normenhansen * @author monceaux, normenhansen
*/ */
public class MjpegFileWriter { public class MjpegFileWriter {
int width = 0; int width = 0;
int height = 0; int height = 0;
double framerate = 0; double framerate = 0;
@ -32,7 +33,7 @@ public class MjpegFileWriter {
public MjpegFileWriter(File aviFile, int width, int height, double framerate) throws Exception { public MjpegFileWriter(File aviFile, int width, int height, double framerate) throws Exception {
this(aviFile, width, height, framerate, 0); this(aviFile, width, height, framerate, 0);
} }
public MjpegFileWriter(File aviFile, int width, int height, double framerate, int numFrames) throws Exception { public MjpegFileWriter(File aviFile, int width, int height, double framerate, int numFrames) throws Exception {
this.aviFile = aviFile; this.aviFile = aviFile;
this.width = width; this.width = width;
@ -55,8 +56,11 @@ public class MjpegFileWriter {
} }
public void addImage(Image image) throws Exception { public void addImage(Image image) throws Exception {
addImage(writeImageToBytes(image));
}
public void addImage(byte[] imagedata) throws Exception {
byte[] fcc = new byte[]{'0', '0', 'd', 'b'}; byte[] fcc = new byte[]{'0', '0', 'd', 'b'};
byte[] imagedata = writeImageToBytes(image);
int useLength = imagedata.length; int useLength = imagedata.length;
long position = aviChannel.position(); long position = aviChannel.position();
int extra = (useLength + (int) position) % 4; int extra = (useLength + (int) position) % 4;
@ -469,16 +473,18 @@ public class MjpegFileWriter {
} }
} }
private byte[] writeImageToBytes(Image image) throws Exception { public byte[] writeImageToBytes(Image image) throws Exception {
BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); BufferedImage bi;
if (image instanceof BufferedImage && ((BufferedImage) image).getType() == BufferedImage.TYPE_INT_RGB) {
bi = (BufferedImage) image;
} else {
bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = bi.createGraphics();
g.drawImage(image, 0, 0, width, height, null);
}
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
Graphics2D g = bi.createGraphics();
g.drawImage(image, 0, 0, width, height, null);
ImageIO.write(bi, "jpg", baos); ImageIO.write(bi, "jpg", baos);
baos.close(); baos.close();
bi = null;
g = null;
return baos.toByteArray(); return baos.toByteArray();
} }
} }

@ -4,6 +4,7 @@ import com.jme3.app.Application;
import com.jme3.post.SceneProcessor; import com.jme3.post.SceneProcessor;
import com.jme3.renderer.Camera; import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager; import com.jme3.renderer.RenderManager;
import com.jme3.renderer.Renderer;
import com.jme3.renderer.ViewPort; import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue; import com.jme3.renderer.queue.RenderQueue;
import com.jme3.system.NanoTimer; import com.jme3.system.NanoTimer;
@ -15,10 +16,9 @@ import java.io.*;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -35,23 +35,31 @@ public class VideoRecorderAppState extends AbstractAppState {
private int framerate = 30; private int framerate = 30;
private VideoProcessor processor; private VideoProcessor processor;
private MjpegFileWriter writer;
private File file; private File file;
private Application app; private Application app;
private ExecutorService executor; private ExecutorService executor = Executors.newCachedThreadPool(new ThreadFactory() {
private Future fut;
public Thread newThread(Runnable r) {
Thread th = new Thread(r);
th.setName("jME Video Processing Thread");
th.setDaemon(true);
return th;
}
});
private ExecutorService writeThread = Executors.newSingleThreadExecutor(new ThreadFactory() {
public Thread newThread(Runnable r) {
Thread th = new Thread(r);
th.setName("jME Video Writing Thread");
th.setDaemon(true);
return th;
}
});
private int numCpus = Runtime.getRuntime().availableProcessors();
public VideoRecorderAppState(File file) { public VideoRecorderAppState(File file) {
this.file = file; this.file = file;
executor = Executors.newSingleThreadExecutor(new ThreadFactory() { Logger.getLogger(this.getClass().getName()).log(Level.INFO, "JME3 VideoRecorder running on {0} CPU's", numCpus);
public Thread newThread(Runnable r) {
Thread th = new Thread(r);
th.setName("jME Video processing Thread");
th.setDaemon(true);
return th;
}
});
} }
public File getFile() { public File getFile() {
@ -74,29 +82,80 @@ public class VideoRecorderAppState extends AbstractAppState {
@Override @Override
public void cleanup() { public void cleanup() {
app.getViewPort().removeProcessor(processor); app.getViewPort().removeProcessor(processor);
app.setTimer(new NanoTimer());
super.cleanup(); super.cleanup();
} }
public class VideoProcessor implements SceneProcessor { private class WorkItem {
ByteBuffer buffer;
BufferedImage image;
byte[] data;
public WorkItem(int width, int height) {
image = new BufferedImage(width, height,
BufferedImage.TYPE_4BYTE_ABGR);
buffer = BufferUtils.createByteBuffer(width * height * 4);
}
}
private class VideoProcessor implements SceneProcessor {
private Camera camera; private Camera camera;
private int width; private int width;
private int height; private int height;
private FrameBuffer frameBuffer;
private RenderManager renderManager; private RenderManager renderManager;
private ByteBuffer byteBuffer;
private BufferedImage rawFrame;
private boolean isInitilized = false; private boolean isInitilized = false;
private LinkedBlockingQueue<WorkItem> freeItems;
private LinkedBlockingQueue<WorkItem> usedItems = new LinkedBlockingQueue<WorkItem>();
private MjpegFileWriter writer;
public void addImage(Renderer renderer, FrameBuffer out) {
if (freeItems == null) {
return;
}
try {
final WorkItem item = freeItems.take();
usedItems.add(item);
item.buffer.clear();
renderer.readFrameBuffer(out, item.buffer);
executor.submit(new Callable<Void>() {
public Void call() throws Exception {
Screenshots.convertScreenShot(item.buffer, item.image);
item.data = writer.writeImageToBytes(item.image);
while (usedItems.peek() != item) {
Thread.sleep(5);
}
writeThread.submit(new Callable<Void>() {
public Void call() throws Exception {
writer.addImage(item.data);
freeItems.add(item);
return null;
}
});
usedItems.poll();
return null;
}
});
} catch (InterruptedException ex) {
Logger.getLogger(VideoRecorderAppState.class.getName()).log(Level.SEVERE, null, ex);
}
}
public void initialize(RenderManager rm, ViewPort viewPort) { public void initialize(RenderManager rm, ViewPort viewPort) {
this.camera = viewPort.getCamera(); this.camera = viewPort.getCamera();
this.width = camera.getWidth(); this.width = camera.getWidth();
this.height = camera.getHeight(); this.height = camera.getHeight();
rawFrame = new BufferedImage(width, height,
BufferedImage.TYPE_4BYTE_ABGR);
byteBuffer = BufferUtils.createByteBuffer(width * height * 4);
this.renderManager = rm; this.renderManager = rm;
this.isInitilized = true; this.isInitilized = true;
if (freeItems == null) {
freeItems = new LinkedBlockingQueue<WorkItem>();
for (int i = 0; i < numCpus; i++) {
freeItems.add(new WorkItem(width, height));
}
}
} }
public void reshape(ViewPort vp, int w, int h) { public void reshape(ViewPort vp, int w, int h) {
@ -120,45 +179,19 @@ public class VideoRecorderAppState extends AbstractAppState {
} }
public void postFrame(FrameBuffer out) { public void postFrame(FrameBuffer out) {
try { addImage(renderManager.getRenderer(), out);
if (fut != null) {
fut.get();
}
fut = null;
} catch (InterruptedException ex) {
} catch (ExecutionException ex) {
}
byteBuffer.clear();
renderManager.getRenderer().readFrameBuffer(out, byteBuffer);
fut = executor.submit(new Callable<Void>() {
public Void call() throws Exception {
Screenshots.convertScreenShot(byteBuffer, rawFrame);
try {
writer.addImage(rawFrame);
} catch (Exception ex) {
Logger.getLogger(VideoRecorderAppState.class.getName()).log(Level.SEVERE, "Error writing frame: {0}", ex);
}
return null;
}
});
} }
public void cleanup() { public void cleanup() {
try { try {
if (fut != null) { while (freeItems.size() < numCpus) {
fut.get(); Thread.sleep(10);
fut = null;
} }
} catch (InterruptedException ex) {
} catch (ExecutionException ex) {
}
app.setTimer(new NanoTimer());
try {
writer.finishAVI(); writer.finishAVI();
} catch (Exception ex) { } catch (Exception ex) {
Logger.getLogger(VideoRecorderAppState.class.getName()).log(Level.SEVERE, "Error closing video: {0}", ex); Logger.getLogger(VideoRecorderAppState.class.getName()).log(Level.SEVERE, "Error closing video: {0}", ex);
} }
writer = null;
} }
} }

Loading…
Cancel
Save