Package org.nlogo.awt

Source Code of org.nlogo.awt.JMFMovieEncoderDataStream

/// LICENSES

// This class is based on sim.util.media.MovieEncoder by Sean Luke:
/**
* MASON Open Source License
*
* License Agreement
*
*
* This software is Copyright 2003 by Sean Luke. Portions Copyright 2003 by
* Gabriel Catalin Balan, Liviu Panait, Sean Paus, and Dan Kuebrich. All
* Rights Reserved
*
* Developed in Conjunction with the George Mason University Center for Social
* Complexity
*
* By using the source code, binary code files, or related data included in
* this distribution, you agree to the following terms of usage for this
* software distribution. All but a few source code files in this distribution
* fall under this license; the exceptions contain open source licenses
* embedded in the source code files themselves. In this license the Authors
* means the Copyright Holders listed above, and the license itself is
* Copyright 2003 by Sean Luke.
*
* The Authors hereby grant you a world-wide, royalty-free, non-exclusive
* license, subject to third party intellectual property claims:
*
* to use, reproduce, modify, display, perform, sublicense and distribute all
* or any portion of the source code or binary form of this software or
* related data with or without modifications, or as part of a larger work;
* and under patents now or hereafter owned or controlled by the Authors, to
* make, have made, use and sell ("Utilize") all or any portion of the source
* code or binary form of this software or related data, but solely to the
* extent that any such patent is reasonably necessary to enable you to
* Utilize all or any portion of the source code or binary form of this
* software or related data, and not to any greater extent that may be
* necessary to Utilize further modifications or combinations.
*
*
* In return you agree to the following conditions:
*
* If you redistribute all or any portion of the source code of this software
* or related data, it must retain the above copyright notice and this license
* and disclaimer. If you redistribute all or any portion of this code in
* binary form, you must include the above copyright notice and this license
* and disclaimer in the documentation and/or other materials provided with
* the distribution, and must indicate the use of this software in a
* prominent, publically accessible location of the larger work. You must not
* use the Authors's names to endorse or promote products derived from this
* software without the specific prior written permission of the Authors.
*
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS, NOR THEIR EMPLOYERS, NOR GEORGE MASON
* UNIVERSITY, BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
**/


/// Some of this code was snarfed from
/// http://java.sun.com/products/java-media/jmf/2.1.1/solutions/JpegImagesToMovie.java
/// Here's the license to that code. -- SL

/*
* @(#)JpegImagesToMovie.java   1.3 01/03/13
*
* Copyright (c) 1999-2001 Sun Microsystems, Inc. All Rights Reserved.
*
* Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
* modify and redistribute this software in source and binary code form,
* provided that i) this copyright notice and license appear on all copies of
* the software; and ii) Licensee does not utilize the software in a manner
* which is disparaging to Sun.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
* IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
* NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
* LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
* OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
* LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
* INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
* CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
* OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGES.
*
* This software is not designed or intended for use in on-line control of
* aircraft, air traffic, aircraft navigation or aircraft communications; or in
* the design, construction, operation or maintenance of any nuclear
* facility. Licensee represents and warrants that it will not use or
* redistribute the Software for such purposes.
*/

package org.nlogo.awt;

import javax.media.Buffer;
import javax.media.ConfigureCompleteEvent;
import javax.media.Controller;
import javax.media.ControllerEvent;
import javax.media.ControllerListener;
import javax.media.DataSink;
import javax.media.EndOfMediaEvent;
import javax.media.Format;
import javax.media.Manager;
import javax.media.MediaLocator;
import javax.media.PrefetchCompleteEvent;
import javax.media.Processor;
import javax.media.RealizeCompleteEvent;
import javax.media.ResourceUnavailableEvent;
import javax.media.Time;
import javax.media.control.TrackControl;
import javax.media.datasink.DataSinkErrorEvent;
import javax.media.datasink.DataSinkEvent;
import javax.media.datasink.DataSinkListener;
import javax.media.datasink.EndOfStreamEvent;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.FileTypeDescriptor;
import javax.media.protocol.PullBufferDataSource;
import javax.media.protocol.PullBufferStream;
import javax.media.util.ImageToBuffer;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.IOException;

/**
* Encodes a sequence of BufferedImages into a QuickTime movie.
* Requires JMF 2.1.1.
* <p>Parts of these classes are loosely based on MovieEncoder.java by Sean Luke
* which is in turn based on JpegImagesToMovie.java from Sun.
* Both licenses are attached.
*/
public strictfp class JMFMovieEncoder
    implements MovieEncoder,
    DataSinkListener, ControllerListener, java.io.Serializable {

  private boolean isSetup = false;
  private boolean stopped = false;

  private float frameRate;
  private final String fileName;

  private Processor processor;
  private JMFMovieEncoderDataSource source;
  private DataSink sink;
  private int numFrames = 0;
  private java.io.File outFile;

  private Dimension frameSize;
  private Format format;
  private int type;

  /**
   * Creates a new movie encoder.
   *
   * @param frameRate frame rate in frames per second.
   */
  public JMFMovieEncoder(int frameRate, String fileName) {
    this.frameRate = frameRate;
    this.fileName = fileName;
  }

  public synchronized void setFrameRate(float frameRate)
      throws IOException {
    if (isSetup) {
      throw new IOException("Can't set framerate after setup");
    }

    this.frameRate = frameRate;
  }

  public synchronized float getFrameRate() {
    return frameRate;
  }

  public synchronized Dimension getFrameSize() {
    return frameSize;
  }

  public synchronized String getFormat() {
    return format.toString();
  }

  public synchronized void add(BufferedImage image)
      throws IOException {

    if (numFrames == 0) {
      setup(image);
    }

    if (!stopped) {
      image = preProcess(image);
      source.add(image);
      numFrames++;
    }
  }

  public boolean isSetup() {
    return isSetup;
  }

  /**
   * Sets everything up. Called the first time an image is added
   * to this movie encoder.
   *
   * @param image a typical image; used to determine frame format and size
   */
  protected synchronized void setup(BufferedImage image)
      throws IOException {
    try {
      frameSize = new Dimension(image.getWidth(), image.getHeight());
      type = image.getType();
      numFrames = 0;

      // Figure out available formats and pick the first one
      // We're using frameRate here but I don't think that should matter
      format = ImageToBuffer.createBuffer(image, frameRate).getFormat();
      source = new JMFMovieEncoderDataSource(format, frameRate);
      processor = Manager.createProcessor(source);
      processor.addControllerListener(this);
      processor.configure();
      if (!waitForState(processor, Processor.Configured)) {
        throw new IOException("JMFMovieEncoder error: Failed to configure processor");
      }

      processor.setContentDescriptor(new ContentDescriptor(FileTypeDescriptor.QUICKTIME));

      TrackControl tcs[] = processor.getTrackControls();

      // Take the first supported format
      Format f[] = tcs[0].getSupportedFormats();

      //System.out.println("Supported formats:");
      //for (int j=0;j<f.length;j++) { System.out.println(f[j]); }

      if (f == null || f.length <= 0) {
        throw new IOException("The mux does not support the input format: " + tcs[0].getFormat());
      }
      tcs[0].setFormat(f[0]);
      format = f[0];

      //System.out.println("Codecs for " + format + ":");
      //Vector v =
      //javax.media.PlugInManager.getPlugInList(format, null,
      //javax.media.PlugInManager.CODEC);
      //java.util.Iterator it = v.iterator();
      //while(it.hasNext()) { System.out.println(it.next()); }

      // Realize the processor
      processor.realize();
      if (!waitForState(processor, Controller.Realized)) {
        throw new IOException("Failed to realize processor");
      }

      // Set up the sink
      outFile = new java.io.File(fileName);
      sink = Manager.createDataSink(processor.getDataOutput(),
          new MediaLocator(toURL(outFile)));
      sink.addDataSinkListener(this);
      sink.open();
      processor.start();
      sink.start();
      isSetup = true;
    } catch (javax.media.MediaException ex) {
      // we recast all media exceptions as ioexceptions since we don't want
      // any other NetLogo class to have to know about JMF
      throw new IOException("Cannot setup movie: " + ex);
    }
  }

  public synchronized void stop() {
    if (stopped) {
      return;
    }

    stopped = true;

    if (isSetup) {
      source.finish();

      waitForFileDone(); // wait for EndOfStream event.
      sink.close();

      processor.removeControllerListener(this);
    }


  }

  public synchronized void cancel() {
    if (stopped) {
      return;
    }
    stopped = true;

    if (isSetup) {
      source.finish();

      waitForFileDone(); // wait for EndOfStream event.
      sink.close();

      processor.removeControllerListener(this);
      stopped = true// just in case the dataSinkUpdate didn't do it -- interruptedException maybe

      // delete the file
      outFile.delete();
    }
  }

  public int getNumFrames() {
    return numFrames;
  }

  /**
   * Ensures that each frame is the same format and size.
   * Called on each image before it's added to the movie.
   */
  protected BufferedImage preProcess(BufferedImage image) {
    if (image.getWidth() != frameSize.width || image.getHeight() != frameSize.height || image.getType() != type) {
      // if there's a size or type mismatch, shlop the image onto one that fits
      BufferedImage temp = new BufferedImage(frameSize.width, frameSize.height, type);
      Graphics2D g = temp.createGraphics();
      g.drawImage(image, 0, 0, null);
      image = temp;
    }
    return image;
  }


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

  /**
   * Blocks until the processor has transitioned to the given state.
   *
   * @return false if the transition failed.
   */
  private boolean waitForState(Processor p, int state) {
    synchronized (waitSync) {
      try {
        while (p.getState() < state && stateTransitionOK) {
          waitSync.wait();
        }
      } catch (InterruptedException ex) {
        ignore(ex);
      }
    }
    return stateTransitionOK;
  }

  /**
   * From ControllerListener.
   */
  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 final Object waitFileSync = new Object();
  private boolean fileDone = false;
  private boolean fileSuccess = true;

  /**
   * Blocks until file writing is done.
   */
  private boolean waitForFileDone() {
    synchronized (waitFileSync) {
      try {
        while (!fileDone) {
          waitFileSync.wait();
        }
      } catch (InterruptedException ex) {
        ignore(ex);
      }
    }
    return fileSuccess;
  }


  /**
   * Event handler for the file writer.
   */
  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 static void ignore(Throwable t) // NOPMD ok to ignore
  {
    // do nothing, but you could put debugging output here,
    // or a debugger breakpoint... - ST 5/14/03
  }

  // for 4.1 we have too much fragile, difficult-to-understand,
  // under-tested code involving URLs -- we can't get rid of our
  // uses of toURL() until 4.2, the risk of breakage is too high.
  // so for now, at least we make this a separate method so the
  // SuppressWarnings annotation is narrowly targeted. - ST 12/7/09
  @SuppressWarnings("deprecation")
  private static java.net.URL toURL(java.io.File file)
      throws java.net.MalformedURLException {
    return file.toURL();
  }

}


/**
* A simple data source wraps up the image stream.
*/
class JMFMovieEncoderDataSource extends PullBufferDataSource {
  JMFMovieEncoderDataStream[] streams;

  public JMFMovieEncoderDataSource(Format format, float frameRate) {
    streams = new JMFMovieEncoderDataStream[1];
    streams[0] = new JMFMovieEncoderDataStream(format, frameRate);
  }

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

  @Override
  public PullBufferStream[] getStreams() {
    return streams;
  }

  @Override
  public Time getDuration() {
    return DURATION_UNKNOWN;
  }

  public void add(Image i) {
    streams[0].write(i);
  }

  public void finish() {
    streams[0].finish();
  }

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

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

  @Override
  public void setLocator(MediaLocator source) {
  }

  @Override
  public MediaLocator getLocator() {
    return null;
  }

  @Override
  public void connect() {
  }

  @Override
  public void disconnect() {
  }

  @Override
  public void start() {
  }

  @Override
  public void stop() {
  }

}

/**
* JMFMovieEncoderDataStream
* Provides the underlying JMF Processor with images (converted to Buffers) for it to
* encode and write out to disk as it sees fit.
*/
// Our stream from which the processor requests images.  Here's how it works.
// You put an image in the stream with write().  This is blocking -- we have to
// wait until any existing image in there has been flushed out.
// Additionally, the underlying processor is reading images [as buffers] with read(),
// and it's blocking waiting for us to provide stuff.  The blocks are handled with
// spin-waits (25ms sleeps) because I'm being lazy. -- SL
class JMFMovieEncoderDataStream implements PullBufferStream {
  // we won't have a buffered mechanism here -- instead we'll assume
  // that there is one, and exactly one, image waiting
  Buffer buffer = null;
  Format format;
  boolean ended = false;
  boolean endAcknowledged = false;
  float frameRate;

  JMFMovieEncoderDataStream(Format format, float frameRate) {
    this.frameRate = frameRate;
    this.format = format;
  }

  void finish() {
    synchronized (this) {
      ended = true;
    }
  }

  // blocks on write
  void write(Image i) {
    Buffer b = ImageToBuffer.createBuffer(i, frameRate);

    // spin-wait, ugh
    while (checkWriteBlock()) {
      try {
        Thread.sleep(25);
      } catch (InterruptedException e) {
        return;
      }
    }
    synchronized (this) {
      buffer = b;
    }
  }

  synchronized boolean checkWriteBlock() {
    return (buffer != null);
  }

  synchronized boolean checkReadBlock() {
    return (buffer == null && !ended);
  }

  public boolean willReadBlock() {
    return false// liar liar pants on fire
  }

  // could block on read
  public void read(Buffer buf) {
    // spin-wait, ugh
    while (checkReadBlock()) {
      try {
        Thread.sleep(25);
      } catch (InterruptedException e) {
        System.out.println("ugh.");
      }
    }

    // Check if we need to close up shop
    synchronized (this) {
      if (ended) {
        // We are done.  Set EndOfMedia.
        buf.setEOM(true);
        buf.setOffset(0);
        buf.setLength(0);
        endAcknowledged = true;
      } else {
        // load the data
        buf.setData(buffer.getData());
        buf.setLength(buffer.getLength());
        buf.setOffset(0);
        buf.setFormat(format);
        buf.setFlags(buf.getFlags() | Buffer.FLAG_KEY_FRAME | Buffer.FLAG_NO_DROP)// must write the frame
      }
      buffer = null;
    }
  }

  // returns the buffered image format
  public Format getFormat() {
    return format;
  }

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

  public boolean endOfStream() {
    return ended; // or should it be endAcknowledged?
  }

  // empty methods
  public long getContentLength() {
    return 0;
  }

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

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

}
TOP

Related Classes of org.nlogo.awt.JMFMovieEncoderDataStream

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.