All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.fmj.media.parser.XmlMovieParser Maven / Gradle / Ivy

There is a newer version: 1.0.2-jitsi
Show newest version
package net.sf.fmj.media.parser;

import java.io.*;
import java.util.*;
import java.util.logging.*;

import javax.media.*;
import javax.media.protocol.*;

import net.sf.fmj.media.*;
import net.sf.fmj.utility.*;

import org.xml.sax.*;
import org.xml.sax.helpers.*;

import com.lti.utils.*;
import com.lti.utils.synchronization.*;

/**
 * Parser for FMJ's XML movie format.
 *
 * @author Ken Larson
 *
 */
public class XmlMovieParser extends AbstractDemultiplexer
{
    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()
        {
        }

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

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

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

        @Override
        public int mapTimeToFrame(Time t)
        {
            return FRAME_UNKNOWN;
        }

        @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);

        }
    }

    private static final Logger logger = LoggerSingleton.logger;

    private ContentDescriptor[] supportedInputContentDescriptors = new ContentDescriptor[] { new ContentDescriptor(
            "video.xml") };

    private PullDataSource source;

    private PullSourceStreamTrack[] tracks;

    private XmlMovieSAXHandler xmlMovieSAXHandler;

    private XmlMovieSAXParserThread xmlMovieSAXParserThread;

    public XmlMovieParser()
    {
        super();
    }

    @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
    public ContentDescriptor[] getSupportedInputContentDescriptors()
    {
        return supportedInputContentDescriptors;
    }

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

    @Override
    public boolean isPositionable()
    {
        return false; // TODO
    }

    // 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 isRandomAccess()
    {
        return super.isRandomAccess(); // TODO: can we determine this from the
                                       // data source?
    }

    // @Override
    // public Time setPosition(Time where, int rounding)
    // {
    // }

    @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 setSource(DataSource source) throws IOException,
            IncompatibleSourceException
    {
        source.getLocator().getProtocol();

        if (!(source instanceof PullDataSource))
            throw new IncompatibleSourceException();

        this.source = (PullDataSource) source;

    }

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

}

/**
 * SAX callback for FMJ's XML movie format.
 *
 * @author Ken Larson
 *
 */
class XmlMovieSAXHandler extends DefaultHandler
{
    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 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 final ProducerConsumerQueue qMeta = new ProducerConsumerQueue();

    private final Map qBuffers = new HashMap();
    private final Map formatsMap = new HashMap();

    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 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 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);
                    }
                }
            }
        }
    }

    @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 Buffer readBuffer(int track) throws 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);
    }

    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);

    }

    @Override
    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("");
                // b.append(StringUtils.byteArrayToHexString((byte[])
                // buffer.getData(), buffer.getLength(), buffer.getOffset()));
                // b.append("");
                // b.append("\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);
        }
    }

}

/**
 * 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;
    }

    @Override
    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();
        }

    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy