Package net.sf.fmj.media.parser

Source Code of net.sf.fmj.media.parser.XmlMovieSAXParserThread

package net.sf.fmj.media.parser;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.media.BadHeaderException;
import javax.media.Buffer;
import javax.media.Duration;
import javax.media.Format;
import javax.media.IncompatibleSourceException;
import javax.media.ResourceUnavailableException;
import javax.media.Time;
import javax.media.Track;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.DataSource;
import javax.media.protocol.PullDataSource;
import javax.media.protocol.PullSourceStream;

import net.sf.fmj.media.AbstractDemultiplexer;
import net.sf.fmj.media.AbstractTrack;
import net.sf.fmj.media.PullSourceStreamInputStream;
import net.sf.fmj.utility.FormatArgUtils;
import net.sf.fmj.utility.LoggerSingleton;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

import com.lti.utils.StringUtils;
import com.lti.utils.synchronization.CloseableThread;
import com.lti.utils.synchronization.ProducerConsumerQueue;

/**
* Parser for FMJ's XML movie format.
* @author Ken Larson
*
*/
public class XmlMovieParser extends AbstractDemultiplexer
{
  private static final Logger logger = LoggerSingleton.logger;
 

  private ContentDescriptor[] supportedInputContentDescriptors = new ContentDescriptor[] {
      new ContentDescriptor("video.xml")
  };
 
 
  private PullDataSource source;
 
  private PullSourceStreamTrack[] tracks;
 
  public XmlMovieParser()
  {  super();
  }
 
 
  @Override
  public ContentDescriptor[] getSupportedInputContentDescriptors()
  {
    return supportedInputContentDescriptors;
  }

 
  @Override
  public Track[] getTracks() throws IOException, BadHeaderException
  {
    return tracks;
  }

 
  @Override
  public void setSource(DataSource source) throws IOException, IncompatibleSourceException
  {
    final String protocol = source.getLocator().getProtocol();
   
    if (!(source instanceof PullDataSource))
      throw new IncompatibleSourceException();
   
    this.source = (PullDataSource) source;
   
  }
 

  private XmlMovieSAXHandler xmlMovieSAXHandler;
  private XmlMovieSAXParserThread xmlMovieSAXParserThread;
 
  @Override
  public void open() throws ResourceUnavailableException
  {
    try
    {
      //source.connect(); // TODO: assume source is already connected
      source.start()// TODO: stop/disconnect on stop/close.
   
      final PullSourceStream[] streams = source.getStreams();
     
      // only first stream supported.
      if (streams.length > 1)
        logger.warning("only 1 stream supported, " + streams.length + " found");
     
      final InputStream is = new PullSourceStreamInputStream(streams[0]);
      xmlMovieSAXHandler = new XmlMovieSAXHandler();
      xmlMovieSAXParserThread = new XmlMovieSAXParserThread(xmlMovieSAXHandler, is);
      xmlMovieSAXParserThread.start()// TODO: stop when done
     
      Format[] formats = xmlMovieSAXHandler.readTracksInfo();
     
     
      tracks = new PullSourceStreamTrack[formats.length];
     
      for (int i = 0; i < formats.length; ++i)
      {
        tracks[i] = new VideoTrack(i, formats[i]);
 
      }
   
    } catch (IOException e)
    {
      logger.log(Level.WARNING, "" + e, e);
      throw new ResourceUnavailableException("" + e);
    } catch (SAXException e)
    {
      logger.log(Level.WARNING, "" + e, e);
      throw new ResourceUnavailableException("" + e);
    } catch (InterruptedException e)
    {
      logger.log(Level.WARNING, "" + e, e);
      throw new ResourceUnavailableException("" + e);
    }

    super.open();

  }

  @Override
  public void close()
  {

    if (tracks != null)
    {
      for (int i = 0; i < tracks.length; ++i)
      {
        if (tracks[i] != null)
        {
          tracks[i].deallocate();
          tracks[i] = null;
        }
      }
      tracks = null;
    }

   
    super.close();
  }

  // @Override
  @Override
  public void start() throws IOException
  {

  }
 
  // TODO: should we stop data source in stop?
//  // @Override
//  public void stop()
//  {
//    try
//    {
//      source.stop();
//    } catch (IOException e)
//    {
//      logger.log(Level.WARNING, "" + e, e);
//    }
//  }
 
 
 
  @Override
  public boolean isPositionable()
  {
    return false// TODO
  }
 
//  @Override
//  public Time setPosition(Time where, int rounding)
//  {
//  }

 
  @Override
  public boolean isRandomAccess()
  {
    return super.isRandomAccess()// TODO: can we determine this from the data source?
  }
 

  private abstract class PullSourceStreamTrack extends AbstractTrack
  {
    public abstract void deallocate();

  }
 


 
  private class VideoTrack extends PullSourceStreamTrack
  {
    // TODO: track listener
   
    private final int track;
    private final Format format;
   

   
    public VideoTrack(int track, Format format) throws ResourceUnavailableException
    {
      super();
   
      this.track = track;
      this.format = format;
    }
   
   
    @Override
    public void deallocate()
    {
    }

    /**
     *
     * @return nanos skipped, 0 if unable to skip.
     * @throws IOException
     */
    public long skipNanos(long nanos) throws IOException
    {
      return 0// TODO
     
    }
   
    public boolean canSkipNanos()
    {
      return false;
    }

    @Override
    public Format getFormat()
    {
      return format;
    }

   
   
   
    @Override
    public void readFrame(Buffer buffer)
    {
      Buffer b;
      try
      {
        b = xmlMovieSAXHandler.readBuffer(track);
      } catch (SAXException e)
      {
        throw new RuntimeException(e);
      } catch (IOException e)
      {
        throw new RuntimeException(e);
      } catch (InterruptedException e)
      {
        throw new RuntimeException(e);
      }
      buffer.copy(b);


    }

   
    @Override
    public Time mapFrameToTime(int frameNumber)
    {
      return TIME_UNKNOWN; 
    }

    @Override
    public int mapTimeToFrame(Time t)
    { 
      return FRAME_UNKNOWN;   
    }
   
 
   
    @Override
    public Time getDuration()
    {
      return Duration.DURATION_UNKNOWN;  // TODO
    }

  }

}


/**
* SAX callback for FMJ's XML movie format.
* @author Ken Larson
*
*/
class XmlMovieSAXHandler extends DefaultHandler
{

  private final ProducerConsumerQueue qMeta = new ProducerConsumerQueue();

  private final Map<Integer, ProducerConsumerQueue> qBuffers = new HashMap<Integer, ProducerConsumerQueue>();

  private final Map<Integer, Format> formatsMap = new HashMap<Integer, Format>();

  private static String getStringAttr(Attributes atts, String qName) throws SAXException
  {
    final int index = atts.getIndex(qName);
    if (index < 0)
      throw new SAXException("Missing attribute: " + qName);
    return getStringAttr(atts, qName, null);
  }
  private static String getStringAttr(Attributes atts, String qName, String defaultResult) throws SAXException
  {
    final int index = atts.getIndex(qName);
    if (index < 0)
      return defaultResult;
    final String s = atts.getValue(index);
    return s;
  }
 
  private static int getIntAttr(Attributes atts, String qName) throws SAXException
  {
    final int index = atts.getIndex(qName);
    if (index < 0)
      throw new SAXException("Missing attribute: " + qName);
    return getIntAttr(atts, qName, 0);
  }
  private static int getIntAttr(Attributes atts, String qName, int defaultResult) throws SAXException
  {
    final int index = atts.getIndex(qName);
    if (index < 0)
      return defaultResult;
    final String s = atts.getValue(index);
    try
    {
      return Integer.parseInt(s);
    }
    catch (NumberFormatException e)
    {  throw new SAXException("Expected integer: " + s, e);
    }
  }
 
  private static long getLongAttr(Attributes atts, String qName) throws SAXException
  {
    final int index = atts.getIndex(qName);
    if (index < 0)
      throw new SAXException("Missing attribute: " + qName);
    return getLongAttr(atts, qName, 0);
  }
  private static long getLongAttr(Attributes atts, String qName, long defaultResult) throws SAXException
  {
    final int index = atts.getIndex(qName);
    if (index < 0)
      return defaultResult;
    final String s = atts.getValue(index);
    try
    {
      return Long.parseLong(s);
    }
    catch (NumberFormatException e)
    {  throw new SAXException("Expected long: " + s, e);
    }
  }
 
  private int currentTrack = -1;
  private Buffer currentBuffer;
  private StringBuilder currentDataChars;
 
  private int state = INIT;
 
  private static final int INIT = 0;
  private static final int AWAIT_BUFFER = 10;
  private static final int AWAIT_DATA = 11;
  private static final int READ_DATA = 12;
 
 
  @Override
  public void endDocument() throws SAXException
  {
   
    if (qBuffers != null)
    {
      for (ProducerConsumerQueue q : qBuffers.values())
      {
        if (q != null)
        { 
          final Buffer eomBuffer = new Buffer();
          eomBuffer.setEOM(true);
          // TODO: set format/
          try
          {
            q.put(eomBuffer);
          } catch (InterruptedException e)
          {
            throw new SAXException(e);
          }
        }
      }
    }
  }
  public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException
  {

    // TODO: verify element nesting
    try
    {
      if (localName.equals("XmlMovie"))
      {  final String version = atts.getValue(atts.getIndex("version"));
        if (!version.equals("1.0"))
          throw new SAXException("Expection XmlMovie version 1.0");
      }
      else if (localName.equals("Track"))
      {
        // TODO: catch exceptions
        final int index = getIntAttr(atts, "index");
        final String formatStr = getStringAttr(atts, "format");
        Format format = FormatArgUtils.parse(formatStr);
        formatsMap.put(index, format);
        qBuffers.put(index, new ProducerConsumerQueue());
      }
     
      else if (localName.equals("Buffer"))
      {
        currentTrack = getIntAttr(atts, "track");
        final long sequenceNumber = getLongAttr(atts, "sequenceNumber", Buffer.SEQUENCE_UNKNOWN);
        final long timeStamp = getLongAttr(atts, "timeStamp");
        final long duration = getLongAttr(atts, "duration", -1L);
        final int flags = getIntAttr(atts, "flags", 0);
       
        final String formatStr = getStringAttr(atts, "format", null);
       
        final Format format = formatStr == null ? formatsMap.get(currentTrack) : FormatArgUtils.parse(formatStr);

        Buffer buffer = new Buffer();
        buffer.setSequenceNumber(sequenceNumber);
        buffer.setTimeStamp(timeStamp);
        buffer.setDuration(duration);
        buffer.setFlags(flags);
        buffer.setFormat(format);
       
        currentBuffer = buffer;  // data will be set when we get the data element
        currentDataChars = new StringBuilder();
        state = AWAIT_DATA;
//         
//        b.append("<Data>");
//        b.append(StringUtils.byteArrayToHexString((byte[]) buffer.getData(), buffer.getLength(), buffer.getOffset()));
//        b.append("</Data>");
//        b.append("</Buffer>\n");
      }
      else if (localName.equals("Data"))
      {
        if (state != AWAIT_DATA)
          throw new SAXException("Not expecting Data element");
        state = READ_DATA;
      }
      // if (namespaceURI.equals("http://recipes.org") &&
      // localName.equals("ingredient")) {
      // String n = atts.getValue("","name");
      // if (n.equals("flour")) {
      // String a = atts.getValue("","amount"); // assume 'amount'
      // exists
      // amount = amount + Float.valueOf(a).floatValue();
      // }
      // }
    }
    catch (SAXException e)
    {  throw e;
    }
    catch (Exception e)
    {
      throw new SAXException(e);
    }
  }

  @Override
  public void characters(char[] ch, int start, int length) throws SAXException
  {
    if (state == READ_DATA)
    {
      String s = new String(ch, start, length);
      currentDataChars.append(s);
    }
    else
   
      String s = new String(ch, start, length);
      s = s.trim();
      if (s.length() > 0)
        throw new SAXException("characters unexpected, state=" + state + " chars=" + s);
    }
  }
  @Override
  public void endElement(String uri, String localName, String name) throws SAXException
  {
    if (localName.equals("Tracks"))
    {   // done with track info
      if (formatsMap.size() == 0)
        throw new SAXException("No tracks");
      try
      {
        final Format[] formatsArray = new Format[formatsMap.size()];
        for (int i = 0; i < formatsArray.length; ++i)
        { 
          final Format format = formatsMap.get(i);
          if (format == null)
            throw new SAXException("Expected format for track " + i);
          formatsArray[i] = format;
       
        }
        qMeta.put(formatsArray);
      } catch (InterruptedException e)
      {
        e.printStackTrace();
      }

    }
    else if (localName.equals("Data"))
    {
      byte[] data = StringUtils.hexStringToByteArray(currentDataChars.toString());
      currentBuffer.setData(data);
      currentBuffer.setOffset(0);
      currentBuffer.setLength(data.length);
     
      try
      {
        qBuffers.get(currentTrack).put(currentBuffer);
      } catch (InterruptedException e)
      {
        throw new SAXException(e)// TODO: there should be an InterruptedSAXException...
      }
      currentBuffer = null;
      currentTrack = -1;
      currentDataChars = null;
     
      state = AWAIT_BUFFER;
    }
  }
 
  public void postError(Exception e) throws InterruptedException
  {
    if (qMeta != null)
      qMeta.put(e);
    if (qBuffers != null)
    {
      for (ProducerConsumerQueue q : qBuffers.values())
      {
        if (q != null)
          q.put(e);
      }
    }
   

  }
 
  public Format[] readTracksInfo() throws SAXException, IOException, InterruptedException
  {
    final Object o = qMeta.get();
    if (o instanceof Format[])
      return (Format[]) o;
    else if (o instanceof SAXException)
      throw (SAXException) o;
    else if (o instanceof IOException)
      throw (IOException) o;
    else
      throw new RuntimeException("Unknown object in queue: " + o);
   
  }
 
  public Buffer readBuffer(int trackthrows SAXException, IOException, InterruptedException
  {
    final Object o = qBuffers.get(track).get();
    if (o instanceof Buffer)
      return (Buffer) o;
    else if (o instanceof SAXException)
      throw (SAXException) o;
    else if (o instanceof IOException)
      throw (IOException) o;
    else
      throw new RuntimeException("Unknown object in queue: " + o);
  }

}

/**
* Thread to parse FMJ's XML movie format, results handled by XmlMovieSAXHandler.  XmlMovieSAXHandler can be used to read parsed results.
* @author Ken Larson
*
*/
class XmlMovieSAXParserThread extends CloseableThread
{
  private final XmlMovieSAXHandler handler;
  private final InputStream is;
 

  public XmlMovieSAXParserThread(XmlMovieSAXHandler handler, InputStream is)
  {
    super();
    this.handler = handler;
    this.is = is;
  }


  public void run()
  {
    try
    {
      try
      {
        final XMLReader parser = XMLReaderFactory.createXMLReader();
        parser.setContentHandler(handler);
   
        parser.parse(new InputSource(is));
      }
      catch (SAXException e)
      {  handler.postError(e);
      } catch (IOException e)
      {
        handler.postError(e);
      }

    }
    catch (InterruptedException e)
    {  // exit thread
    }
    finally
    setClosed();
    }

  }
}

TOP

Related Classes of net.sf.fmj.media.parser.XmlMovieSAXParserThread

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.