Selaa lähdekoodia

Buffered mjpeg file writer (#1592)

* Write buffered

* IOException

* Don't force flush, maintain position info manually

* Optimize array initial sizes, usually image is bigger than the buffer so write as one chunk
Toni Helenius 4 vuotta sitten
vanhempi
commit
520971c0e7
1 muutettua tiedostoa jossa 78 lisäystä ja 58 poistoa
  1. 78 58
      jme3-desktop/src/main/java/com/jme3/app/state/MjpegFileWriter.java

+ 78 - 58
jme3-desktop/src/main/java/com/jme3/app/state/MjpegFileWriter.java

@@ -34,11 +34,16 @@ package com.jme3.app.state;
 import java.awt.Graphics2D;
 import java.awt.Image;
 import java.awt.image.BufferedImage;
+import java.io.BufferedOutputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
-import java.io.RandomAccessFile;
-import java.nio.channels.FileChannel;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.Files;
+import java.nio.file.StandardOpenOption;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -52,17 +57,17 @@ import javax.imageio.stream.ImageOutputStream;
  * Released under BSD License
  * @author monceaux, normenhansen, entrusC
  */
-public class MjpegFileWriter {
+public class MjpegFileWriter implements AutoCloseable {
 
     int width = 0;
     int height = 0;
     double framerate = 0;
     int numFrames = 0;
     File aviFile = null;
-    FileOutputStream aviOutput = null;
-    FileChannel aviChannel = null;
+    OutputStream aviOutput = null;
     long riffOffset = 0;
     long aviMovieOffset = 0;
+    long position = 0;
     AVIIndexList indexlist = null;
 
     public MjpegFileWriter(File aviFile, int width, int height, double framerate) throws Exception {
@@ -75,25 +80,31 @@ public class MjpegFileWriter {
         this.height = height;
         this.framerate = framerate;
         this.numFrames = numFrames;
-        aviOutput = new FileOutputStream(aviFile);
-        aviChannel = aviOutput.getChannel();
+        FileOutputStream fos = new FileOutputStream(aviFile);
+        aviOutput = new BufferedOutputStream(fos);
 
         RIFFHeader rh = new RIFFHeader();
-        aviOutput.write(rh.toBytes());
-        aviOutput.write(new AVIMainHeader().toBytes());
-        aviOutput.write(new AVIStreamList().toBytes());
-        aviOutput.write(new AVIStreamHeader().toBytes());
-        aviOutput.write(new AVIStreamFormat().toBytes());
-        aviOutput.write(new AVIJunk().toBytes());
-        aviMovieOffset = aviChannel.position();
-        aviOutput.write(new AVIMovieList().toBytes());
+        ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);
+        baos.write(rh.toBytes());
+        baos.write(new AVIMainHeader().toBytes());
+        baos.write(new AVIStreamList().toBytes());
+        baos.write(new AVIStreamHeader().toBytes());
+        baos.write(new AVIStreamFormat().toBytes());
+        baos.write(new AVIJunk().toBytes());
+        byte[] headerBytes = baos.toByteArray();
+        aviOutput.write(headerBytes);
+        aviMovieOffset = headerBytes.length;
+        byte[] listBytes = new AVIMovieList().toBytes();
+        aviOutput.write(listBytes);
         indexlist = new AVIIndexList();
+
+        position = headerBytes.length + listBytes.length;
     }
 
     public void addImage(Image image) throws Exception {
         addImage(image, 0.8f);
     }
-    
+
     public void addImage(Image image, float quality) throws Exception {
         addImage(writeImageToBytes(image, quality));
     }
@@ -101,7 +112,6 @@ public class MjpegFileWriter {
     public void addImage(byte[] imagedata) throws Exception {
         byte[] fcc = new byte[]{'0', '0', 'd', 'b'};
         int useLength = imagedata.length;
-        long position = aviChannel.position();
         int extra = (useLength + (int) position) % 4;
         if (extra > 0) {
             useLength = useLength + extra;
@@ -109,39 +119,44 @@ public class MjpegFileWriter {
 
         indexlist.addAVIIndex((int) position, useLength);
 
-        aviOutput.write(fcc);
-        aviOutput.write(intBytes(swapInt(useLength)));
-        aviOutput.write(imagedata);
+        ByteArrayOutputStream baos = new ByteArrayOutputStream(fcc.length + 4 + useLength);
+        baos.write(fcc);
+        baos.write(intBytes(swapInt(useLength)));
+        baos.write(imagedata);
         if (extra > 0) {
             for (int i = 0; i < extra; i++) {
-                aviOutput.write(0);
+                baos.write(0);
             }
         }
+        byte[] data = baos.toByteArray();
+        aviOutput.write(data);
         imagedata = null;
-        
+
         numFrames++; //add a frame
+        position += data.length;
     }
 
-    public void finishAVI() throws Exception {
+    public void finishAVI() throws IOException {
         byte[] indexlistBytes = indexlist.toBytes();
         aviOutput.write(indexlistBytes);
         aviOutput.close();
-        int fileSize = (int)aviFile.length();
+        int fileSize = (int) aviFile.length();
         int listSize = (int) (fileSize - 8 - aviMovieOffset - indexlistBytes.length);
-        
-        RandomAccessFile raf = new RandomAccessFile(aviFile, "rw");
-        
+
         //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();
+        try (SeekableByteChannel sbc = Files.newByteChannel(aviFile.toPath(), StandardOpenOption.WRITE);
+                ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+            baos.write(new RIFFHeader(fileSize).toBytes());
+            baos.write(new AVIMainHeader().toBytes());
+            baos.write(new AVIStreamList().toBytes());
+            baos.write(new AVIStreamHeader().toBytes());
+            baos.write(new AVIStreamFormat().toBytes());
+            baos.write(new AVIJunk().toBytes());
+            baos.write(new AVIMovieList(listSize).toBytes());
+
+            sbc.write(ByteBuffer.wrap(baos.toByteArray()));
+        }
     }
 
     // public void writeAVI(File file) throws Exception
@@ -181,6 +196,11 @@ public class MjpegFileWriter {
         return b;
     }
 
+    @Override
+    public void close() throws Exception {
+        finishAVI();
+    }
+
     private class RIFFHeader {
 
         public byte[] fcc = new byte[]{'R', 'I', 'F', 'F'};
@@ -192,12 +212,12 @@ public class MjpegFileWriter {
 
         public RIFFHeader() {
         }
-        
+
         public RIFFHeader(int fileSize) {
             this.fileSize = fileSize;
         }
 
-        public byte[] toBytes() throws Exception {
+        public byte[] toBytes() throws IOException {
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
             baos.write(fcc);
             baos.write(intBytes(swapInt(fileSize)));
@@ -212,7 +232,7 @@ public class MjpegFileWriter {
 
     private class AVIMainHeader {
         /*
-         * 
+         *
          * FOURCC fcc; DWORD cb; DWORD dwMicroSecPerFrame; DWORD
          * dwMaxBytesPerSec; DWORD dwPaddingGranularity; DWORD dwFlags; DWORD
          * dwTotalFrames; DWORD dwInitialFrames; DWORD dwStreams; DWORD
@@ -256,7 +276,7 @@ public class MjpegFileWriter {
             dwTotalFrames = numFrames;
         }
 
-        public byte[] toBytes() throws Exception {
+        public byte[] toBytes() throws IOException {
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
             baos.write(fcc);
             baos.write(intBytes(swapInt(cb)));
@@ -288,7 +308,7 @@ public class MjpegFileWriter {
         public AVIStreamList() {
         }
 
-        public byte[] toBytes() throws Exception {
+        public byte[] toBytes() throws IOException {
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
             baos.write(fcc);
             baos.write(intBytes(swapInt(size)));
@@ -341,7 +361,7 @@ public class MjpegFileWriter {
             dwLength = numFrames;
         }
 
-        public byte[] toBytes() throws Exception {
+        public byte[] toBytes() throws IOException {
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
             baos.write(fcc);
             baos.write(intBytes(swapInt(cb)));
@@ -401,7 +421,7 @@ public class MjpegFileWriter {
             biSizeImage = width * height;
         }
 
-        public byte[] toBytes() throws Exception {
+        public byte[] toBytes() throws IOException {
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
             baos.write(fcc);
             baos.write(intBytes(swapInt(cb)));
@@ -434,8 +454,8 @@ public class MjpegFileWriter {
         public AVIMovieList(int listSize) {
             this.listSize = listSize;
         }
-        
-        public byte[] toBytes() throws Exception {
+
+        public byte[] toBytes() throws IOException {
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
             baos.write(fcc);
             baos.write(intBytes(swapInt(listSize)));
@@ -463,7 +483,7 @@ public class MjpegFileWriter {
             ind.add(new AVIIndex(dwOffset, dwSize));
         }
 
-        public byte[] toBytes() throws Exception {
+        public byte[] toBytes() throws IOException {
             cb = 16 * ind.size();
 
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -490,7 +510,7 @@ public class MjpegFileWriter {
             this.dwSize = dwSize;
         }
 
-        public byte[] toBytes() throws Exception {
+        public byte[] toBytes() throws IOException {
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
             baos.write(fcc);
             baos.write(intBytes(swapInt(dwFlags)));
@@ -511,7 +531,7 @@ public class MjpegFileWriter {
             Arrays.fill(data, (byte) 0);
         }
 
-        public byte[] toBytes() throws Exception {
+        public byte[] toBytes() throws IOException {
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
             baos.write(fcc);
             baos.write(intBytes(swapInt(size)));
@@ -532,16 +552,16 @@ public class MjpegFileWriter {
         }
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 
-        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();
-        
+        ImageWriter imgWrtr = ImageIO.getImageWritersByFormatName("jpg").next();
+        try (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);
+        }
+
         return baos.toByteArray();
     }
 }