- improve multithreading in VideoRecorderAppState
git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@8713 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
This commit is contained in:
parent
2253690024
commit
04e348f0fb
@ -18,6 +18,7 @@ import javax.imageio.ImageIO;
|
||||
* @author monceaux, normenhansen
|
||||
*/
|
||||
public class MjpegFileWriter {
|
||||
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
double framerate = 0;
|
||||
@ -32,7 +33,7 @@ public class MjpegFileWriter {
|
||||
public MjpegFileWriter(File aviFile, int width, int height, double framerate) throws Exception {
|
||||
this(aviFile, width, height, framerate, 0);
|
||||
}
|
||||
|
||||
|
||||
public MjpegFileWriter(File aviFile, int width, int height, double framerate, int numFrames) throws Exception {
|
||||
this.aviFile = aviFile;
|
||||
this.width = width;
|
||||
@ -55,8 +56,11 @@ public class MjpegFileWriter {
|
||||
}
|
||||
|
||||
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[] imagedata = writeImageToBytes(image);
|
||||
int useLength = imagedata.length;
|
||||
long position = aviChannel.position();
|
||||
int extra = (useLength + (int) position) % 4;
|
||||
@ -469,16 +473,18 @@ public class MjpegFileWriter {
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] writeImageToBytes(Image image) throws Exception {
|
||||
BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
public byte[] writeImageToBytes(Image image) throws Exception {
|
||||
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();
|
||||
Graphics2D g = bi.createGraphics();
|
||||
g.drawImage(image, 0, 0, width, height, null);
|
||||
ImageIO.write(bi, "jpg", baos);
|
||||
baos.close();
|
||||
bi = null;
|
||||
g = null;
|
||||
|
||||
return baos.toByteArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import com.jme3.app.Application;
|
||||
import com.jme3.post.SceneProcessor;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.renderer.Renderer;
|
||||
import com.jme3.renderer.ViewPort;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.system.NanoTimer;
|
||||
@ -15,10 +16,9 @@ import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
@ -35,23 +35,31 @@ public class VideoRecorderAppState extends AbstractAppState {
|
||||
|
||||
private int framerate = 30;
|
||||
private VideoProcessor processor;
|
||||
private MjpegFileWriter writer;
|
||||
private File file;
|
||||
private Application app;
|
||||
private ExecutorService executor;
|
||||
private Future fut;
|
||||
private ExecutorService executor = Executors.newCachedThreadPool(new ThreadFactory() {
|
||||
|
||||
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) {
|
||||
this.file = file;
|
||||
executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
|
||||
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread th = new Thread(r);
|
||||
th.setName("jME Video processing Thread");
|
||||
th.setDaemon(true);
|
||||
return th;
|
||||
}
|
||||
});
|
||||
Logger.getLogger(this.getClass().getName()).log(Level.INFO, "JME3 VideoRecorder running on {0} CPU's", numCpus);
|
||||
}
|
||||
|
||||
public File getFile() {
|
||||
@ -74,29 +82,80 @@ public class VideoRecorderAppState extends AbstractAppState {
|
||||
@Override
|
||||
public void cleanup() {
|
||||
app.getViewPort().removeProcessor(processor);
|
||||
app.setTimer(new NanoTimer());
|
||||
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 int width;
|
||||
private int height;
|
||||
private FrameBuffer frameBuffer;
|
||||
private RenderManager renderManager;
|
||||
private ByteBuffer byteBuffer;
|
||||
private BufferedImage rawFrame;
|
||||
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) {
|
||||
this.camera = viewPort.getCamera();
|
||||
this.width = camera.getWidth();
|
||||
this.height = camera.getHeight();
|
||||
rawFrame = new BufferedImage(width, height,
|
||||
BufferedImage.TYPE_4BYTE_ABGR);
|
||||
byteBuffer = BufferUtils.createByteBuffer(width * height * 4);
|
||||
this.renderManager = rm;
|
||||
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) {
|
||||
@ -120,45 +179,19 @@ public class VideoRecorderAppState extends AbstractAppState {
|
||||
}
|
||||
|
||||
public void postFrame(FrameBuffer out) {
|
||||
try {
|
||||
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;
|
||||
}
|
||||
});
|
||||
addImage(renderManager.getRenderer(), out);
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
try {
|
||||
if (fut != null) {
|
||||
fut.get();
|
||||
fut = null;
|
||||
while (freeItems.size() < numCpus) {
|
||||
Thread.sleep(10);
|
||||
}
|
||||
} catch (InterruptedException ex) {
|
||||
} catch (ExecutionException ex) {
|
||||
}
|
||||
app.setTimer(new NanoTimer());
|
||||
try {
|
||||
writer.finishAVI();
|
||||
} catch (Exception ex) {
|
||||
Logger.getLogger(VideoRecorderAppState.class.getName()).log(Level.SEVERE, "Error closing video: {0}", ex);
|
||||
}
|
||||
writer = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user