Home | History | Annotate | Download | only in state
      1 package com.jme3.app.state;
      2 
      3 import java.awt.Graphics2D;
      4 import java.awt.Image;
      5 import java.awt.image.BufferedImage;
      6 import java.io.ByteArrayOutputStream;
      7 import java.io.File;
      8 import java.io.FileOutputStream;
      9 import java.io.RandomAccessFile;
     10 import java.nio.channels.FileChannel;
     11 import java.util.ArrayList;
     12 import java.util.Arrays;
     13 import java.util.List;
     14 import javax.imageio.ImageIO;
     15 
     16 /**
     17  * Released under BSD License
     18  * @author monceaux, normenhansen
     19  */
     20 public class MjpegFileWriter {
     21 
     22     int width = 0;
     23     int height = 0;
     24     double framerate = 0;
     25     int numFrames = 0;
     26     File aviFile = null;
     27     FileOutputStream aviOutput = null;
     28     FileChannel aviChannel = null;
     29     long riffOffset = 0;
     30     long aviMovieOffset = 0;
     31     AVIIndexList indexlist = null;
     32 
     33     public MjpegFileWriter(File aviFile, int width, int height, double framerate) throws Exception {
     34         this(aviFile, width, height, framerate, 0);
     35     }
     36 
     37     public MjpegFileWriter(File aviFile, int width, int height, double framerate, int numFrames) throws Exception {
     38         this.aviFile = aviFile;
     39         this.width = width;
     40         this.height = height;
     41         this.framerate = framerate;
     42         this.numFrames = numFrames;
     43         aviOutput = new FileOutputStream(aviFile);
     44         aviChannel = aviOutput.getChannel();
     45 
     46         RIFFHeader rh = new RIFFHeader();
     47         aviOutput.write(rh.toBytes());
     48         aviOutput.write(new AVIMainHeader().toBytes());
     49         aviOutput.write(new AVIStreamList().toBytes());
     50         aviOutput.write(new AVIStreamHeader().toBytes());
     51         aviOutput.write(new AVIStreamFormat().toBytes());
     52         aviOutput.write(new AVIJunk().toBytes());
     53         aviMovieOffset = aviChannel.position();
     54         aviOutput.write(new AVIMovieList().toBytes());
     55         indexlist = new AVIIndexList();
     56     }
     57 
     58     public void addImage(Image image) throws Exception {
     59         addImage(writeImageToBytes(image));
     60     }
     61 
     62     public void addImage(byte[] imagedata) throws Exception {
     63         byte[] fcc = new byte[]{'0', '0', 'd', 'b'};
     64         int useLength = imagedata.length;
     65         long position = aviChannel.position();
     66         int extra = (useLength + (int) position) % 4;
     67         if (extra > 0) {
     68             useLength = useLength + extra;
     69         }
     70 
     71         indexlist.addAVIIndex((int) position, useLength);
     72 
     73         aviOutput.write(fcc);
     74         aviOutput.write(intBytes(swapInt(useLength)));
     75         aviOutput.write(imagedata);
     76         if (extra > 0) {
     77             for (int i = 0; i < extra; i++) {
     78                 aviOutput.write(0);
     79             }
     80         }
     81         imagedata = null;
     82     }
     83 
     84     public void finishAVI() throws Exception {
     85         byte[] indexlistBytes = indexlist.toBytes();
     86         aviOutput.write(indexlistBytes);
     87         aviOutput.close();
     88         long size = aviFile.length();
     89         RandomAccessFile raf = new RandomAccessFile(aviFile, "rw");
     90         raf.seek(4);
     91         raf.write(intBytes(swapInt((int) size - 8)));
     92         raf.seek(aviMovieOffset + 4);
     93         raf.write(intBytes(swapInt((int) (size - 8 - aviMovieOffset - indexlistBytes.length))));
     94         raf.close();
     95     }
     96 
     97     // public void writeAVI(File file) throws Exception
     98     // {
     99     // OutputStream os = new FileOutputStream(file);
    100     //
    101     // // RIFFHeader
    102     // // AVIMainHeader
    103     // // AVIStreamList
    104     // // AVIStreamHeader
    105     // // AVIStreamFormat
    106     // // write 00db and image bytes...
    107     // }
    108     public static int swapInt(int v) {
    109         return (v >>> 24) | (v << 24) | ((v << 8) & 0x00FF0000) | ((v >> 8) & 0x0000FF00);
    110     }
    111 
    112     public static short swapShort(short v) {
    113         return (short) ((v >>> 8) | (v << 8));
    114     }
    115 
    116     public static byte[] intBytes(int i) {
    117         byte[] b = new byte[4];
    118         b[0] = (byte) (i >>> 24);
    119         b[1] = (byte) ((i >>> 16) & 0x000000FF);
    120         b[2] = (byte) ((i >>> 8) & 0x000000FF);
    121         b[3] = (byte) (i & 0x000000FF);
    122 
    123         return b;
    124     }
    125 
    126     public static byte[] shortBytes(short i) {
    127         byte[] b = new byte[2];
    128         b[0] = (byte) (i >>> 8);
    129         b[1] = (byte) (i & 0x000000FF);
    130 
    131         return b;
    132     }
    133 
    134     private class RIFFHeader {
    135 
    136         public byte[] fcc = new byte[]{'R', 'I', 'F', 'F'};
    137         public int fileSize = 0;
    138         public byte[] fcc2 = new byte[]{'A', 'V', 'I', ' '};
    139         public byte[] fcc3 = new byte[]{'L', 'I', 'S', 'T'};
    140         public int listSize = 200;
    141         public byte[] fcc4 = new byte[]{'h', 'd', 'r', 'l'};
    142 
    143         public RIFFHeader() {
    144         }
    145 
    146         public byte[] toBytes() throws Exception {
    147             ByteArrayOutputStream baos = new ByteArrayOutputStream();
    148             baos.write(fcc);
    149             baos.write(intBytes(swapInt(fileSize)));
    150             baos.write(fcc2);
    151             baos.write(fcc3);
    152             baos.write(intBytes(swapInt(listSize)));
    153             baos.write(fcc4);
    154             baos.close();
    155 
    156             return baos.toByteArray();
    157         }
    158     }
    159 
    160     private class AVIMainHeader {
    161         /*
    162          *
    163          * FOURCC fcc; DWORD cb; DWORD dwMicroSecPerFrame; DWORD
    164          * dwMaxBytesPerSec; DWORD dwPaddingGranularity; DWORD dwFlags; DWORD
    165          * dwTotalFrames; DWORD dwInitialFrames; DWORD dwStreams; DWORD
    166          * dwSuggestedBufferSize; DWORD dwWidth; DWORD dwHeight; DWORD
    167          * dwReserved[4];
    168          */
    169 
    170         public byte[] fcc = new byte[]{'a', 'v', 'i', 'h'};
    171         public int cb = 56;
    172         public int dwMicroSecPerFrame = 0;                                // (1
    173         // /
    174         // frames
    175         // per
    176         // sec)
    177         // *
    178         // 1,000,000
    179         public int dwMaxBytesPerSec = 10000000;
    180         public int dwPaddingGranularity = 0;
    181         public int dwFlags = 65552;
    182         public int dwTotalFrames = 0;                                // replace
    183         // with
    184         // correct
    185         // value
    186         public int dwInitialFrames = 0;
    187         public int dwStreams = 1;
    188         public int dwSuggestedBufferSize = 0;
    189         public int dwWidth = 0;                                // replace
    190         // with
    191         // correct
    192         // value
    193         public int dwHeight = 0;                                // replace
    194         // with
    195         // correct
    196         // value
    197         public int[] dwReserved = new int[4];
    198 
    199         public AVIMainHeader() {
    200             dwMicroSecPerFrame = (int) ((1.0 / framerate) * 1000000.0);
    201             dwWidth = width;
    202             dwHeight = height;
    203             dwTotalFrames = numFrames;
    204         }
    205 
    206         public byte[] toBytes() throws Exception {
    207             ByteArrayOutputStream baos = new ByteArrayOutputStream();
    208             baos.write(fcc);
    209             baos.write(intBytes(swapInt(cb)));
    210             baos.write(intBytes(swapInt(dwMicroSecPerFrame)));
    211             baos.write(intBytes(swapInt(dwMaxBytesPerSec)));
    212             baos.write(intBytes(swapInt(dwPaddingGranularity)));
    213             baos.write(intBytes(swapInt(dwFlags)));
    214             baos.write(intBytes(swapInt(dwTotalFrames)));
    215             baos.write(intBytes(swapInt(dwInitialFrames)));
    216             baos.write(intBytes(swapInt(dwStreams)));
    217             baos.write(intBytes(swapInt(dwSuggestedBufferSize)));
    218             baos.write(intBytes(swapInt(dwWidth)));
    219             baos.write(intBytes(swapInt(dwHeight)));
    220             baos.write(intBytes(swapInt(dwReserved[0])));
    221             baos.write(intBytes(swapInt(dwReserved[1])));
    222             baos.write(intBytes(swapInt(dwReserved[2])));
    223             baos.write(intBytes(swapInt(dwReserved[3])));
    224             baos.close();
    225 
    226             return baos.toByteArray();
    227         }
    228     }
    229 
    230     private class AVIStreamList {
    231 
    232         public byte[] fcc = new byte[]{'L', 'I', 'S', 'T'};
    233         public int size = 124;
    234         public byte[] fcc2 = new byte[]{'s', 't', 'r', 'l'};
    235 
    236         public AVIStreamList() {
    237         }
    238 
    239         public byte[] toBytes() throws Exception {
    240             ByteArrayOutputStream baos = new ByteArrayOutputStream();
    241             baos.write(fcc);
    242             baos.write(intBytes(swapInt(size)));
    243             baos.write(fcc2);
    244             baos.close();
    245 
    246             return baos.toByteArray();
    247         }
    248     }
    249 
    250     private class AVIStreamHeader {
    251         /*
    252          * FOURCC fcc; DWORD cb; FOURCC fccType; FOURCC fccHandler; DWORD
    253          * dwFlags; WORD wPriority; WORD wLanguage; DWORD dwInitialFrames; DWORD
    254          * dwScale; DWORD dwRate; DWORD dwStart; DWORD dwLength; DWORD
    255          * dwSuggestedBufferSize; DWORD dwQuality; DWORD dwSampleSize; struct {
    256          * short int left; short int top; short int right; short int bottom; }
    257          * rcFrame;
    258          */
    259 
    260         public byte[] fcc = new byte[]{'s', 't', 'r', 'h'};
    261         public int cb = 64;
    262         public byte[] fccType = new byte[]{'v', 'i', 'd', 's'};
    263         public byte[] fccHandler = new byte[]{'M', 'J', 'P', 'G'};
    264         public int dwFlags = 0;
    265         public short wPriority = 0;
    266         public short wLanguage = 0;
    267         public int dwInitialFrames = 0;
    268         public int dwScale = 0;                                // microseconds
    269         // per
    270         // frame
    271         public int dwRate = 1000000;                          // dwRate
    272         // /
    273         // dwScale
    274         // =
    275         // frame
    276         // rate
    277         public int dwStart = 0;
    278         public int dwLength = 0;                                // num
    279         // frames
    280         public int dwSuggestedBufferSize = 0;
    281         public int dwQuality = -1;
    282         public int dwSampleSize = 0;
    283         public int left = 0;
    284         public int top = 0;
    285         public int right = 0;
    286         public int bottom = 0;
    287 
    288         public AVIStreamHeader() {
    289             dwScale = (int) ((1.0 / framerate) * 1000000.0);
    290             dwLength = numFrames;
    291         }
    292 
    293         public byte[] toBytes() throws Exception {
    294             ByteArrayOutputStream baos = new ByteArrayOutputStream();
    295             baos.write(fcc);
    296             baos.write(intBytes(swapInt(cb)));
    297             baos.write(fccType);
    298             baos.write(fccHandler);
    299             baos.write(intBytes(swapInt(dwFlags)));
    300             baos.write(shortBytes(swapShort(wPriority)));
    301             baos.write(shortBytes(swapShort(wLanguage)));
    302             baos.write(intBytes(swapInt(dwInitialFrames)));
    303             baos.write(intBytes(swapInt(dwScale)));
    304             baos.write(intBytes(swapInt(dwRate)));
    305             baos.write(intBytes(swapInt(dwStart)));
    306             baos.write(intBytes(swapInt(dwLength)));
    307             baos.write(intBytes(swapInt(dwSuggestedBufferSize)));
    308             baos.write(intBytes(swapInt(dwQuality)));
    309             baos.write(intBytes(swapInt(dwSampleSize)));
    310             baos.write(intBytes(swapInt(left)));
    311             baos.write(intBytes(swapInt(top)));
    312             baos.write(intBytes(swapInt(right)));
    313             baos.write(intBytes(swapInt(bottom)));
    314             baos.close();
    315 
    316             return baos.toByteArray();
    317         }
    318     }
    319 
    320     private class AVIStreamFormat {
    321         /*
    322          * FOURCC fcc; DWORD cb; DWORD biSize; LONG biWidth; LONG biHeight; WORD
    323          * biPlanes; WORD biBitCount; DWORD biCompression; DWORD biSizeImage;
    324          * LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD
    325          * biClrImportant;
    326          */
    327 
    328         public byte[] fcc = new byte[]{'s', 't', 'r', 'f'};
    329         public int cb = 40;
    330         public int biSize = 40;                               // same
    331         // as
    332         // cb
    333         public int biWidth = 0;
    334         public int biHeight = 0;
    335         public short biPlanes = 1;
    336         public short biBitCount = 24;
    337         public byte[] biCompression = new byte[]{'M', 'J', 'P', 'G'};
    338         public int biSizeImage = 0;                                // width
    339         // x
    340         // height
    341         // in
    342         // pixels
    343         public int biXPelsPerMeter = 0;
    344         public int biYPelsPerMeter = 0;
    345         public int biClrUsed = 0;
    346         public int biClrImportant = 0;
    347 
    348         public AVIStreamFormat() {
    349             biWidth = width;
    350             biHeight = height;
    351             biSizeImage = width * height;
    352         }
    353 
    354         public byte[] toBytes() throws Exception {
    355             ByteArrayOutputStream baos = new ByteArrayOutputStream();
    356             baos.write(fcc);
    357             baos.write(intBytes(swapInt(cb)));
    358             baos.write(intBytes(swapInt(biSize)));
    359             baos.write(intBytes(swapInt(biWidth)));
    360             baos.write(intBytes(swapInt(biHeight)));
    361             baos.write(shortBytes(swapShort(biPlanes)));
    362             baos.write(shortBytes(swapShort(biBitCount)));
    363             baos.write(biCompression);
    364             baos.write(intBytes(swapInt(biSizeImage)));
    365             baos.write(intBytes(swapInt(biXPelsPerMeter)));
    366             baos.write(intBytes(swapInt(biYPelsPerMeter)));
    367             baos.write(intBytes(swapInt(biClrUsed)));
    368             baos.write(intBytes(swapInt(biClrImportant)));
    369             baos.close();
    370 
    371             return baos.toByteArray();
    372         }
    373     }
    374 
    375     private class AVIMovieList {
    376 
    377         public byte[] fcc = new byte[]{'L', 'I', 'S', 'T'};
    378         public int listSize = 0;
    379         public byte[] fcc2 = new byte[]{'m', 'o', 'v', 'i'};
    380 
    381         // 00db size jpg image data ...
    382         public AVIMovieList() {
    383         }
    384 
    385         public byte[] toBytes() throws Exception {
    386             ByteArrayOutputStream baos = new ByteArrayOutputStream();
    387             baos.write(fcc);
    388             baos.write(intBytes(swapInt(listSize)));
    389             baos.write(fcc2);
    390             baos.close();
    391 
    392             return baos.toByteArray();
    393         }
    394     }
    395 
    396     private class AVIIndexList {
    397 
    398         public byte[] fcc = new byte[]{'i', 'd', 'x', '1'};
    399         public int cb = 0;
    400         public List<AVIIndex> ind = new ArrayList<AVIIndex>();
    401 
    402         public AVIIndexList() {
    403         }
    404 
    405         @SuppressWarnings("unused")
    406         public void addAVIIndex(AVIIndex ai) {
    407             ind.add(ai);
    408         }
    409 
    410         public void addAVIIndex(int dwOffset, int dwSize) {
    411             ind.add(new AVIIndex(dwOffset, dwSize));
    412         }
    413 
    414         public byte[] toBytes() throws Exception {
    415             cb = 16 * ind.size();
    416 
    417             ByteArrayOutputStream baos = new ByteArrayOutputStream();
    418             baos.write(fcc);
    419             baos.write(intBytes(swapInt(cb)));
    420             for (int i = 0; i < ind.size(); i++) {
    421                 AVIIndex in = (AVIIndex) ind.get(i);
    422                 baos.write(in.toBytes());
    423             }
    424 
    425             baos.close();
    426 
    427             return baos.toByteArray();
    428         }
    429     }
    430 
    431     private class AVIIndex {
    432 
    433         public byte[] fcc = new byte[]{'0', '0', 'd', 'b'};
    434         public int dwFlags = 16;
    435         public int dwOffset = 0;
    436         public int dwSize = 0;
    437 
    438         public AVIIndex(int dwOffset, int dwSize) {
    439             this.dwOffset = dwOffset;
    440             this.dwSize = dwSize;
    441         }
    442 
    443         public byte[] toBytes() throws Exception {
    444             ByteArrayOutputStream baos = new ByteArrayOutputStream();
    445             baos.write(fcc);
    446             baos.write(intBytes(swapInt(dwFlags)));
    447             baos.write(intBytes(swapInt(dwOffset)));
    448             baos.write(intBytes(swapInt(dwSize)));
    449             baos.close();
    450 
    451             return baos.toByteArray();
    452         }
    453     }
    454 
    455     private class AVIJunk {
    456 
    457         public byte[] fcc = new byte[]{'J', 'U', 'N', 'K'};
    458         public int size = 1808;
    459         public byte[] data = new byte[size];
    460 
    461         public AVIJunk() {
    462             Arrays.fill(data, (byte) 0);
    463         }
    464 
    465         public byte[] toBytes() throws Exception {
    466             ByteArrayOutputStream baos = new ByteArrayOutputStream();
    467             baos.write(fcc);
    468             baos.write(intBytes(swapInt(size)));
    469             baos.write(data);
    470             baos.close();
    471 
    472             return baos.toByteArray();
    473         }
    474     }
    475 
    476     public byte[] writeImageToBytes(Image image) throws Exception {
    477         BufferedImage bi;
    478         if (image instanceof BufferedImage && ((BufferedImage) image).getType() == BufferedImage.TYPE_INT_RGB) {
    479             bi = (BufferedImage) image;
    480         } else {
    481             bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    482             Graphics2D g = bi.createGraphics();
    483             g.drawImage(image, 0, 0, width, height, null);
    484         }
    485         ByteArrayOutputStream baos = new ByteArrayOutputStream();
    486         ImageIO.write(bi, "jpg", baos);
    487         baos.close();
    488         return baos.toByteArray();
    489     }
    490 }
    491