Selaa lähdekoodia

Add variable quality and video file frame count to VideoRecorderAppState, thanks to @entrusc

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@9283 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
nor..67 13 vuotta sitten
vanhempi
sitoutus
904d835742

+ 46 - 9
engine/src/desktop/com/jme3/app/state/MjpegFileWriter.java

@@ -11,11 +11,15 @@ import java.nio.channels.FileChannel;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import javax.imageio.IIOImage;
 import javax.imageio.ImageIO;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.ImageWriter;
+import javax.imageio.stream.ImageOutputStream;
 
 /**
  * Released under BSD License
- * @author monceaux, normenhansen
+ * @author monceaux, normenhansen, entrusC
  */
 public class MjpegFileWriter {
 
@@ -56,7 +60,11 @@ public class MjpegFileWriter {
     }
 
     public void addImage(Image image) throws Exception {
-        addImage(writeImageToBytes(image));
+        addImage(image, 0.8f);
+    }
+    
+    public void addImage(Image image, float quality) throws Exception {
+        addImage(writeImageToBytes(image, quality));
     }
 
     public void addImage(byte[] imagedata) throws Exception {
@@ -79,18 +87,29 @@ public class MjpegFileWriter {
             }
         }
         imagedata = null;
+        
+        numFrames++; //add a frame
     }
 
     public void finishAVI() throws Exception {
         byte[] indexlistBytes = indexlist.toBytes();
         aviOutput.write(indexlistBytes);
         aviOutput.close();
-        long size = aviFile.length();
+        int fileSize = (int)aviFile.length();
+        int listSize = (int) (fileSize - 8 - aviMovieOffset - indexlistBytes.length);
+        
         RandomAccessFile raf = new RandomAccessFile(aviFile, "rw");
-        raf.seek(4);
-        raf.write(intBytes(swapInt((int) size - 8)));
-        raf.seek(aviMovieOffset + 4);
-        raf.write(intBytes(swapInt((int) (size - 8 - aviMovieOffset - indexlistBytes.length))));
+        
+        //add header and length by writing the headers again
+        //with the now available information
+        raf.write(new RIFFHeader(fileSize).toBytes());
+        raf.write(new AVIMainHeader().toBytes());
+        raf.write(new AVIStreamList().toBytes());
+        raf.write(new AVIStreamHeader().toBytes());
+        raf.write(new AVIStreamFormat().toBytes());
+        raf.write(new AVIJunk().toBytes());
+        raf.write(new AVIMovieList(listSize).toBytes());     
+        
         raf.close();
     }
 
@@ -142,6 +161,10 @@ public class MjpegFileWriter {
 
         public RIFFHeader() {
         }
+        
+        public RIFFHeader(int fileSize) {
+            this.fileSize = fileSize;
+        }
 
         public byte[] toBytes() throws Exception {
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -382,6 +405,10 @@ public class MjpegFileWriter {
         public AVIMovieList() {
         }
 
+        public AVIMovieList(int listSize) {
+            this.listSize = listSize;
+        }
+        
         public byte[] toBytes() throws Exception {
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
             baos.write(fcc);
@@ -473,7 +500,7 @@ public class MjpegFileWriter {
         }
     }
 
-    public byte[] writeImageToBytes(Image image) throws Exception {
+    public byte[] writeImageToBytes(Image image, float quality) throws Exception {
         BufferedImage bi;
         if (image instanceof BufferedImage && ((BufferedImage) image).getType() == BufferedImage.TYPE_INT_RGB) {
             bi = (BufferedImage) image;
@@ -483,7 +510,17 @@ public class MjpegFileWriter {
             g.drawImage(image, 0, 0, width, height, null);
         }
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        ImageIO.write(bi, "jpg", baos);
+
+        ImageWriter imgWrtr = ImageIO.getImageWritersByFormatName("jpg").next();        
+        ImageOutputStream imgOutStrm = ImageIO.createImageOutputStream(baos);
+        imgWrtr.setOutput(imgOutStrm);
+        
+        ImageWriteParam jpgWrtPrm = imgWrtr.getDefaultWriteParam();
+        jpgWrtPrm.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+        jpgWrtPrm.setCompressionQuality(quality);        
+        imgWrtr.write(null, new IIOImage(bi, null, null), jpgWrtPrm);
+        imgOutStrm.close();
+        
         baos.close();
         return baos.toByteArray();
     }

+ 49 - 3
engine/src/desktop/com/jme3/app/state/VideoRecorderAppState.java

@@ -1,6 +1,8 @@
 package com.jme3.app.state;
 
 import com.jme3.app.Application;
+import com.jme3.app.state.AbstractAppState;
+import com.jme3.app.state.AppStateManager;
 import com.jme3.post.SceneProcessor;
 import com.jme3.renderer.Camera;
 import com.jme3.renderer.RenderManager;
@@ -27,7 +29,7 @@ import java.util.logging.Logger;
  * state is detached, else the old file will be overwritten. If you specify no file
  * the AppState will attempt to write a file into the user home directory, made unique
  * by a timestamp.
- * @author normenhansen, Robert McIntyre
+ * @author normenhansen, Robert McIntyre, entrusC
  */
 public class VideoRecorderAppState extends AbstractAppState {
 
@@ -46,13 +48,41 @@ public class VideoRecorderAppState extends AbstractAppState {
     });
     private int numCpus = Runtime.getRuntime().availableProcessors();
     private ViewPort lastViewPort;
+    private float quality;
 
+    /**
+     * Using this constructor the video files will be written sequentially to the user's home directory with
+     * a quality of 0.8
+     */
     public VideoRecorderAppState() {
-        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "JME3 VideoRecorder running on {0} CPU's", numCpus);
+        this(null, 0.8f);
+    }
+    
+    /**
+     * Using this constructor the video files will be written sequentially to the user's home directory.
+     * @param quality the quality of the jpegs in the video stream (0.0 smallest file - 1.0 largest file)
+     */
+    public VideoRecorderAppState(float quality) {
+        this(null, quality);
     }
 
+    /**
+     * This constructor allows you to specify the output file of the video. The quality is set
+     * to 0.8
+     * @param file the video file
+     */
     public VideoRecorderAppState(File file) {
+        this(file, 0.8f);
+    }
+    
+    /**
+     * This constructor allows you to specify the output file of the video as well as the quality
+     * @param file the video file
+     * @param quality the quality of the jpegs in the video stream (0.0 smallest file - 1.0 largest file)
+     */
+    public VideoRecorderAppState(File file, float quality) {
         this.file = file;
+        this.quality = quality;
         Logger.getLogger(this.getClass().getName()).log(Level.INFO, "JME3 VideoRecorder running on {0} CPU's", numCpus);
     }
 
@@ -67,6 +97,22 @@ public class VideoRecorderAppState extends AbstractAppState {
         this.file = file;
     }
 
+    /**
+     * Get the quality used to compress the video images.
+     * @return the quality of the jpegs in the video stream (0.0 smallest file - 1.0 largest file)
+     */
+    public float getQuality() {
+        return quality;
+    }
+
+    /**
+     * Set the video image quality from 0(worst/smallest) to 1(best/largest).
+     * @param quality the quality of the jpegs in the video stream (0.0 smallest file - 1.0 largest file)
+     */
+    public void setQuality(float quality) {
+        this.quality = quality;
+    }
+    
     @Override
     public void initialize(AppStateManager stateManager, Application app) {
         super.initialize(stateManager, app);
@@ -128,7 +174,7 @@ public class VideoRecorderAppState extends AbstractAppState {
 
                     public Void call() throws Exception {
                         Screenshots.convertScreenShot(item.buffer, item.image);
-                        item.data = writer.writeImageToBytes(item.image);
+                        item.data = writer.writeImageToBytes(item.image, quality);
                         while (usedItems.peek() != item) {
                             Thread.sleep(1);
                         }