|
|
@ -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; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|