JMFでQuickTimeムービーを作成する

ざっぱ〜んのプログラムで、QuickTimeムービーを作成してみました。
ということで、そのQuickTimeムービー作成部分。
JMFを使うので、ここからJMFのライブラリをとってきて、jmf.jarをクラスパスに含める必要があります。
Oracle Technology Network for Java Developers | Oracle Technology Network | Oracle
今回はムービーの再生などは行わないので、適当なPlatformを選んでOptional FilesのCross-platform Java版を使っておくと、面倒なJMFインストールの必要がありません。


JMFを使って動画ファイルを作成するサンプルはここです
Oracle Technology Network for Java Developers | Oracle Technology Network | Oracle


けど、このソースはそのまま他の用途で使える形ではないので、汎用で使えるものを作ってみました。
下のImageToMovクラスを使うとこんな感じでコードが書けます。

    public static void main(String[] arg){
        ImageReader ir = new ImageToMov.ImageReader() {
            public BufferedImage getImage(int idx) {
                if(idx >= 4) return null;
                BufferedImage img = new BufferedImage(400, 300, BufferedImage.TYPE_INT_RGB);
                Graphics g = img.getGraphics();
                g.setColor(Color.WHITE);
                g.fillRect(0, 0, 400, 300);

                g.setColor(Color.GREEN);
                if(idx >= 1){
                    g.drawLine(0, 0, 400, 300);
                }

                if(idx >= 2){
                    g.drawLine(0, 300, 400, 0);
                }
                if(idx >= 3){
                    g.setColor(Color.BLUE);
                    g.fillOval(150, 100, 100, 100);
                }
                g.dispose();
                return img;
            }
        };
        String filename = "C:\\Users\\naoki\\Desktop\\test.mov";
        ImageToMov.createMovFromImages(filename, 400, 300, 1, ir);
    }


そうすると、こんな感じのmovファイルができます。


ImageToMovクラスのソースはこんな感じで

//ImageToMov.java
public class ImageToMov{
    /** このインタフェースをimplementsして画像取得処理を記述 */    
    public interface ImageReader{
        /**
         * 画像を返す
         * @param idx 画像番号
         * @return 終わってるときにはnullを返す
         */
        BufferedImage getImage(int idx);
    }    
    
    /** このメソッドを呼び出す */
    public static void createMovFromImages(String filename, 
        int width, int height, float frameRate, ImageReader ir)
    {
        ImageDataSource ids = new ImageDataSource(width, height, frameRate, ir);
        Processor p;
        try{
            p = Manager.createProcessor(ids);
        }catch(Exception e){
            System.out.println(e.getMessage());
            return;
        }
        
        MyControllerListener cl = new MyControllerListener();
        p.addControllerListener(cl);
        p.configure();

        if(!cl.waitForState(p, p.Configured)){
            System.out.println("processorの設定失敗");
            return;
        }
        
        p.setContentDescriptor(new ContentDescriptor(FileTypeDescriptor.QUICKTIME));
        
        TrackControl[] tcs = p.getTrackControls();
        Format[] f = tcs[0].getSupportedFormats();
        if(f == null || f.length <= 0){
            System.out.println(tcs[0].getFormat() + "がサポートされてない");
            return;
        }
        
        tcs[0].setFormat(f[0]);
        
        p.realize();
        if(!cl.waitForState(p, p.Realized)){
            System.out.println("realizeに失敗");
            return;
        }
        
	MediaLocator oml = null;
        try{
            oml = new MediaLocator(new File(filename).toURI().toURL());
        }catch(MalformedURLException e){
        }
	if (oml == null) {
	    System.err.println("Cannot build media locator");
	    return;
	}        
        
        DataSource ds = p.getDataOutput();
        if(ds == null){
            System.out.println("datasourceに失敗");
            return;
        }
        DataSink sink = null;
        try{
            sink = Manager.createDataSink(ds, oml);
            sink.open();
        }catch(Exception e){
            e.printStackTrace();
            System.exit(-1);
        }
        
        MyDataSinkListener dsl = new MyDataSinkListener();
        sink.addDataSinkListener(dsl);
        
        p.start();
        try{
            sink.start();
        }catch(IOException e){
            System.exit(0);
        }
        
        dsl.waitForFileDone();
        
        sink.close();
        p.removeControllerListener(cl);
    }
    
    private static class MyControllerListener implements ControllerListener{

        Object waitSync = new Object();
        boolean stateTransitionOK = true;

        public void controllerUpdate(ControllerEvent evt) {
            if (evt instanceof ConfigureCompleteEvent ||
                evt instanceof RealizeCompleteEvent ||
                evt instanceof PrefetchCompleteEvent) {
                synchronized (waitSync) {
                    stateTransitionOK = true;
                    waitSync.notifyAll();
                }
            } else if (evt instanceof ResourceUnavailableEvent) {
                synchronized (waitSync) {
                    stateTransitionOK = false;
                    waitSync.notifyAll();
                }
            } else if (evt instanceof EndOfMediaEvent) {
                evt.getSourceController().stop();
                evt.getSourceController().close();
            }
        }

        private boolean waitForState(Processor p, int state) {
            synchronized (waitSync) {
                try {
                    while (p.getState() < state && stateTransitionOK)
                        waitSync.wait();
                } catch (Exception e) {}
            }
            return stateTransitionOK;
        }    
    
    }
    
    private static class MyDataSinkListener implements DataSinkListener{
       
        Object waitFileSync = new Object();
        boolean fileDone = false;
        boolean fileSuccess = true;
        
        public void dataSinkUpdate(DataSinkEvent evt) {
            if (evt instanceof EndOfStreamEvent) {
                synchronized (waitFileSync) {
                    fileDone = true;
                    waitFileSync.notifyAll();
                }
            } else if (evt instanceof DataSinkErrorEvent) {
                synchronized (waitFileSync) {
                    fileDone = true;
                    fileSuccess = false;
                    waitFileSync.notifyAll();
                }
            }
        }

        private boolean waitForFileDone() {
            synchronized (waitFileSync) {
                try {
                    while (!fileDone)
                        waitFileSync.wait();
                } catch (Exception e) {}
            }
            return fileSuccess;
        }
        
    }
    
    private static class ImageDataSource extends PullBufferDataSource{
        ImageSourceStream[] streams;

        public ImageDataSource(int width, int height, float frameRate, ImageReader ir) {
            streams = new ImageSourceStream[]{
                new ImageSourceStream(width, height, ir, frameRate) };
        }
        
        @Override
        public PullBufferStream[] getStreams() {
            return streams;
        }

        @Override
        public String getContentType() {
            return ContentDescriptor.RAW;
        }

        @Override
        public void connect() throws IOException {
        }

        @Override
        public void disconnect() {
        }

        @Override
        public void start() throws IOException {
        }

        @Override
        public void stop() throws IOException {
        }

        @Override
        public Object getControl(String arg0) {
            return null;
        }

        @Override
        public Object[] getControls() {
            return new Object[0];
        }

        @Override
        public Time getDuration() {
            return DURATION_UNKNOWN;
        }
        
    }
    
    private static class ImageSourceStream implements PullBufferStream{
        VideoFormat format;
        boolean ended;
        ImageReader imagereader;
        int idx = 0;

        public ImageSourceStream(int width, int height, ImageReader ir, float frameRate) {
            format = new VideoFormat(VideoFormat.JPEG,
                    new Dimension(width, height), Format.NOT_SPECIFIED, Format.byteArray,
                    frameRate);
            imagereader = ir;
        }
        
        public boolean willReadBlock() {
            return false;
        }

        public void read(Buffer buf) throws IOException {
            //画像読み込み処理
            //処理を考える必要があるのはここだけ。
            BufferedImage img = imagereader.getImage(idx);
            idx++;
            if(img == null){
                //終わってる
                buf.setEOM(true);
                buf.setOffset(0);
                buf.setLength(0);
                ended = true;
                return;
            }
            
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ImageIO.write(img, "JPEG", baos);
            byte[] data = baos.toByteArray();
            byte[] b = null;
            if(buf.getData() instanceof byte[]){
                b = (byte[]) buf.getData();
            }
            if(b == null || b.length < data.length){
                b = new byte[data.length];
                buf.setData(b);
            }
            for(int i = 0; i < data.length; ++i){
                b[i] = data[i];
            }
            
            buf.setData(data);
            buf.setOffset(0);
            buf.setLength(data.length);
            buf.setFormat(format);
            buf.setFlags(buf.getFlags() | Buffer.FLAG_KEY_FRAME);
        }

        public Format getFormat() {
            return format;
        }

        public ContentDescriptor getContentDescriptor() {
            return new ContentDescriptor(ContentDescriptor.RAW);
        }

        public long getContentLength() {
            return 0;
        }

        public boolean endOfStream() {
            return ended;
        }

        public Object[] getControls() {
            return new Object[0];
        }

        public Object getControl(String arg0) {
            return null;
        }
        
    }
}