From 04e348f0fbf6ac5099cef1b683778d84f5e37dee Mon Sep 17 00:00:00 2001 From: "nor..67" Date: Thu, 17 Nov 2011 21:17:54 +0000 Subject: [PATCH] - improve multithreading in VideoRecorderAppState git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@8713 75d07b2b-3a1a-0410-a2c5-0572b91ccdca --- .../com/jme3/app/state/MjpegFileWriter.java | 26 ++-- .../jme3/app/state/VideoRecorderAppState.java | 135 +++++++++++------- 2 files changed, 100 insertions(+), 61 deletions(-) diff --git a/engine/src/desktop/com/jme3/app/state/MjpegFileWriter.java b/engine/src/desktop/com/jme3/app/state/MjpegFileWriter.java index 65956f26b..e39bca16d 100644 --- a/engine/src/desktop/com/jme3/app/state/MjpegFileWriter.java +++ b/engine/src/desktop/com/jme3/app/state/MjpegFileWriter.java @@ -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(); - } + } } diff --git a/engine/src/desktop/com/jme3/app/state/VideoRecorderAppState.java b/engine/src/desktop/com/jme3/app/state/VideoRecorderAppState.java index 044709a17..1a6f2b3d6 100644 --- a/engine/src/desktop/com/jme3/app/state/VideoRecorderAppState.java +++ b/engine/src/desktop/com/jme3/app/state/VideoRecorderAppState.java @@ -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 freeItems; + private LinkedBlockingQueue usedItems = new LinkedBlockingQueue(); + 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() { + + 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() { + + 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(); + 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() { - - 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; } }