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

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

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

import java.awt.*;
import java.io.*;
import java.util.*;

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

import net.sf.fmj.media.*;
import net.sf.fmj.media.protocol.*;
import net.sf.fmj.media.rtp.*;

/**
 * Parser for a raw stream of buffers from a PushBufferDataSource.
 */
public class RawPushBufferParser extends RawStreamParser
{
    class FrameTrack implements Track, BufferTransferHandler
    {
        Demultiplexer parser;
        PushBufferStream pbs;
        boolean enabled = true;
        CircularBuffer bufferQ;
        Format format = null;
        TrackListener listener;
        boolean stopped = true;
        boolean closed = false;
        boolean keyFrameFound = false;
        boolean checkDepacketizer = false;
        Depacketizer depacketizer = null;
        Object keyFrameLock = new Object();

        public FrameTrack(Demultiplexer parser, PushBufferStream pbs,
                int numOfBufs)
        {
            this.pbs = pbs;
            format = pbs.getFormat();

            if (source instanceof DelegateDataSource || !isRTPFormat(format))
            {
                keyFrameFound = true;
            }

            bufferQ = new CircularBuffer(numOfBufs);
            pbs.setTransferHandler(this);

            // System.err.println("Track format is " + format);
        }

        public void close()
        {
            // Unblock the transfer handlers.
            setEnabled(false);
            synchronized (bufferQ)
            {
                closed = true;
                bufferQ.notifyAll();
            }
        }

        private Depacketizer findDepacketizer(String name, Format input)
        {
            Class cls;
            Object obj;

            try
            {
                cls = BasicPlugIn.getClassForName(name);
                obj = cls.newInstance();

                if (!(obj instanceof Depacketizer))
                    return null;

                Depacketizer dpktizer = (Depacketizer) obj;

                if (dpktizer.setInputFormat(input) == null)
                    return null;

                dpktizer.open();

                return dpktizer;

            } catch (Exception e)
            {
            } catch (Error e)
            {
            }

            return null;
        }

        /**
         * Parse the RTP/H261 stream Code taken from
         * com.sun.media.codec.video.h261.NativeDecoder.
         */
        public boolean findH261Key(Buffer b)
        {
            int width, height, offset, skipBytes;
            byte data[];

            if ((data = (byte[]) b.getData()) == null)
                return false;

            offset = b.getOffset();

            // Get to the actual h261 compressed payload
            skipBytes = 4;

            if ((data[offset + skipBytes] != 0)
                    || (data[offset + skipBytes + 1] != 1)
                    || ((data[offset + skipBytes + 2] & 0xfc) != 0))
            {
                return false;
            }

            int s = (data[offset + skipBytes + 3] >> 3) & 0x01;
            width = h261Widths[s];
            height = h261Heights[s];
            format = new VideoFormat(VideoFormat.H261_RTP, new Dimension(width,
                    height), ((VideoFormat) format).getMaxDataLength(),
                    ((VideoFormat) format).getDataType(),
                    ((VideoFormat) format).getFrameRate());

            b.setFormat(format);
            return true;
        }

        /**
         * Parse the RTP/H263-1998 stream Code taken from
         * com.sun.media.codec.video.h263.NativeDecoder.
         */
        public boolean findH263_1998Key(Buffer b)
        {
            int width, height, payloadLen, offset;
            byte data[];
            int s = -1;
            int picOffset = -1;

            if ((data = (byte[]) b.getData()) == null)
                return false;

            offset = b.getOffset();

            // 2 bytes for H263-1998 header + pLen from header
            payloadLen = 2 + (((data[offset] & 0x01) << 5) | ((data[offset + 1] & 0xf8) >> 3));
            if ((data[offset] & 0x02) != 0)
            { // Video Redundancy present
                payloadLen++;
            }

            picOffset = -1;

            if (payloadLen > 5)
            {
                // Use PIC header in payload header
                if (((data[offset] & 0x02) == 0x02)
                        && ((data[offset + 3] & 0xfc) == 0x80))
                {
                    picOffset = offset + 3;
                } else if ((data[offset + 2] & 0xfc) == 0x80)
                {
                    picOffset = offset + 2;
                }
            } else if (((data[offset] & 0x04) == 0x04)
                    && ((data[offset + payloadLen] & 0xfc) == 0x80))
            {
                picOffset = offset + payloadLen;
            }

            if (picOffset < 0)
                return false;

            s = (data[picOffset + 2] >> 2) & 0x7;
            if (s == 7)
            {
                // Extended PTYPE, picture size is in the extension
                // if UFEP = 001
                if (((data[picOffset + 3] >> 1) & 0x07) == 1)
                {
                    s = ((data[picOffset + 3] << 2) & 0x04)
                            | ((data[picOffset + 4] >> 6) & 0x03);
                } else
                {
                    return false; // picture type not present
                }
            }
            if (s < 0)
                return false;

            width = h263Widths[s];
            height = h263Heights[s];

            format = new VideoFormat(VideoFormat.H263_1998_RTP, new Dimension(
                    width, height), ((VideoFormat) format).getMaxDataLength(),
                    ((VideoFormat) format).getDataType(),
                    ((VideoFormat) format).getFrameRate());
            b.setFormat(format);
            return true;
        }

        /**
         * Parse the RTP/H263 stream Code taken from
         * com.ibm.media.codec.video.h263.JavaDecoder.
         */
        public boolean findH263Key(Buffer b)
        {
            int width, height, payloadLen, offset;
            byte data[];

            if ((data = (byte[]) b.getData()) == null)
                return false;

            payloadLen = getH263PayloadHeaderLength(data, b.getOffset());
            offset = b.getOffset();
            if ((data[offset + payloadLen] != 0)
                    || (data[offset + payloadLen + 1] != 0)
                    || ((data[offset + payloadLen + 2] & 0xfc) != 0x80))
                return false;

            int s = (data[offset + payloadLen + 4] >> 2) & 0x7;
            width = h263Widths[s];
            height = h263Heights[s];

            format = new VideoFormat(VideoFormat.H263_RTP, new Dimension(width,
                    height), ((VideoFormat) format).getMaxDataLength(),
                    ((VideoFormat) format).getDataType(),
                    ((VideoFormat) format).getFrameRate());

            b.setFormat(format);
            return true;
        }

        /**
         * Parse the RTP/JPEG stream Code taken from
         * com.sun.media.codec.video.jpeg.RTPDePacketizer.
         */
        public boolean findJPEGKey(Buffer b)
        {
            if ((b.getFlags() & Buffer.FLAG_RTP_MARKER) == 0)
                return false;

            int width, height;
            byte data[];

            data = (byte[]) b.getData();
            width = (data[b.getOffset() + 6] & 0xff) * 8;
            height = (data[b.getOffset() + 7] & 0xff) * 8;

            format = new VideoFormat(VideoFormat.JPEG_RTP, new Dimension(width,
                    height), ((VideoFormat) format).getMaxDataLength(),
                    ((VideoFormat) format).getDataType(),
                    ((VideoFormat) format).getFrameRate());

            b.setFormat(format);
            return true;
        }

        private boolean findKeyFrame(Buffer buf)
        {
            if (!checkDepacketizer)
            {
                // Check to see if there's a depacketizer associated with the
                // format. If so, we'll use it for parsing the input.
                Vector pnames = PlugInManager.getPlugInList(buf.getFormat(),
                        null, Depacketizer.DEPACKETIZER);
                if (pnames.size() != 0)
                {
                    depacketizer = findDepacketizer(
                            (String) pnames.elementAt(0), buf.getFormat());
                }
                checkDepacketizer = true;
            }

            Format fmt = buf.getFormat();

            if (fmt == null)
                return false;

            if (fmt.getEncoding() == null)
            {
                synchronized (keyFrameLock)
                {
                    keyFrameFound = true;
                    keyFrameLock.notifyAll();
                }
                return true;
            }

            boolean rtn = true;

            if (jpegVideo.matches(fmt))
                rtn = findJPEGKey(buf);
            else if (h261Video.matches(fmt))
                rtn = findH261Key(buf);
            else if (h263Video.matches(fmt))
                rtn = findH263Key(buf);
            // else if (h263_1998Video.matches(fmt))
            // rtn = findH263_1998Key(buf);
            else if (mpegVideo.matches(fmt))
                rtn = findMPEGKey(buf);
            else if (mpegAudio.matches(fmt))
                rtn = findMPAKey(buf);
            else if (depacketizer != null)
            {
                fmt = depacketizer.parse(buf);
                if (fmt != null)
                {
                    // Found the format. We are done with
                    // the depacketizer.
                    format = fmt;
                    buf.setFormat(format);
                    depacketizer.close();
                    depacketizer = null;
                } else
                    rtn = false;
            }

            if (rtn)
            {
                synchronized (keyFrameLock)
                {
                    keyFrameFound = true;
                    keyFrameLock.notifyAll();
                }
            }

            return keyFrameFound;
        }

        /**
         * Parse the RTP/MPEG audio stream Code taken from
         * com.sun.media.codec.audio.mpa.DePacketizer.
         */
        public boolean findMPAKey(Buffer b)
        {
            int channels;
            double sampleRate;
            byte data[];

            if ((data = (byte[]) b.getData()) == null)
                return false;

            int off = b.getOffset();
            if (b.getLength() < 8)
                return false; // doesn't contain MPA header

            if (data[off + 2] != 0 || data[off + 3] != 0)
                return false; // frame continuation

            off += 4; // skip RTP header to get to MPA header
            if ((data[off] & 0xff) != 0xff || (data[off + 1] & 0xf6) <= 0xf0
                    || (data[off + 2] & 0xf0) == 0xf0
                    || (data[off + 2] & 0x0c) == 0x0c
                    || (data[off + 3] & 0x03) == 0x02)
                return false; // doesn't start with a valid MPA header

            int id = (data[off + 1] >> 3) & 1; // MPEG1 or MPEG2
            int six = (data[off + 2] >> 2) & 3;
            channels = (((data[off + 3] >> 6) & 3) == 3) ? 1 : 2;
            sampleRate = MPASampleTbl[id][six];

            format = new AudioFormat(AudioFormat.MPEG_RTP, sampleRate, 16,
                    channels, AudioFormat.LITTLE_ENDIAN, AudioFormat.SIGNED);

            b.setFormat(format);
            return true;
        }

        /**
         * Parse the RTP/MPEG video stream Code taken from
         * com.sun.media.codec.video.mpeg.DePacketizer.
         */
        public boolean findMPEGKey(Buffer b)
        {
            int width, height;
            float frameRate;
            byte data[];

            if ((data = (byte[]) b.getData()) == null)
            {
                return false;
            }

            int off = b.getOffset();
            if (b.getLength() < 12)
            {
                return false; // can't contain MPEG sequence header
            }

            if ((data[off + 2] & 0x20) != 0x20)
            {
                return false; // doesn't contain MPEG sequence header
            }

            if (data[off + 4] != 0 || data[off + 5] != 0 || data[off + 6] != 1
                    || (data[off + 7] & 0xff) != 0xb3)
            {
                return false; // doesn't start with MPEG sequence header
            }

            int frix = (data[off + 11] & 0x0f);
            if (frix == 0 || frix > 8)
            {
                return false; // not a valid frame rate
            }

            width = ((data[off + 8] & 0xff) << 4)
                    | ((data[off + 9] & 0xf0) >> 4);
            height = ((data[off + 9] & 0x0f) << 8) | (data[off + 10] & 0xff);
            frameRate = MPEGRateTbl[frix];

            format = new VideoFormat(VideoFormat.MPEG_RTP, new Dimension(width,
                    height), ((VideoFormat) format).getMaxDataLength(),
                    ((VideoFormat) format).getDataType(), frameRate);

            b.setFormat(format);
            return true;
        }

        public Time getDuration()
        {
            return parser.getDuration();
        }

        public Format getFormat()
        {
            return format;
        }

        int getH263PayloadHeaderLength(byte[] input, int offset)
        {
            int l = 0;
            byte b = input[offset];

            if ((b & 0x80) != 0)
            { // mode B or C
                if ((b & 0x40) != 0) // mode C
                    l = 12;
                else
                    // mode B
                    l = 8;
            } else
            { // mode A
                l = 4;
            }

            return l;
        }

        public Time getStartTime()
        {
            return new Time(0);
        }

        public boolean isEnabled()
        {
            return enabled;
        }

        public Time mapFrameToTime(int frameNumber)
        {
            return new Time(0);
        }

        public int mapTimeToFrame(Time t)
        {
            return -1;
        }

        /**
         * Peek into the data stream to parse the data format.
         */
        public void parse()
        {
            try
            {
                synchronized (keyFrameLock)
                {
                    while (!keyFrameFound)
                        keyFrameLock.wait();
                }
            } catch (Exception e)
            {
            }
        }

        public void readFrame(Buffer buffer)
        {
            // Retrieve a filled buffer.
            if (stopped)
            {
                buffer.setDiscard(true);
                buffer.setFormat(format);
                return;
            }

            Buffer filled;
            synchronized (bufferQ)
            {
                while (!bufferQ.canRead())
                {
                    try
                    {
                        bufferQ.wait();
                        if (stopped)
                        {
                            buffer.setDiscard(true);
                            buffer.setFormat(format);
                            return;
                        }
                    } catch (Exception e)
                    {
                    }
                }
                filled = bufferQ.read();
            }

            // Copy all the attributes from filled to buffer.
            Object hdr = buffer.getHeader();
            buffer.copy(filled, true);
            filled.setHeader(hdr);

            // if ((buffer.getFlags() & Buffer.FLAG_RTP_MARKER) != 0)
            // System.err.println("RBP: TS: " + buffer.getTimeStamp());

            // Update the saved format.
            format = filled.getFormat();

            synchronized (bufferQ)
            {
                bufferQ.readReport();
                bufferQ.notifyAll();
            }
        }

        public void reset()
        {
        }

        public void setEnabled(boolean t)
        {
            if (t)
                pbs.setTransferHandler(this);
            else
                pbs.setTransferHandler(null);
            enabled = t;
        }

        public void setTrackListener(TrackListener l)
        {
            listener = l;
        }

        public void start()
        {
            // we need to ensure that readFrame is returned to its
            // original state and does not return w/o blocking.
            synchronized (bufferQ)
            {
                stopped = false;
                if (source instanceof CaptureDevice)
                {
                    // Flush the buffer Q.
                    while (bufferQ.canRead())
                    {
                        bufferQ.read();
                        bufferQ.readReport();
                    }
                }
                bufferQ.notifyAll();
            }
        }

        public void stop()
        {
            // we basically need to ensure that readFrame will return
            // immediately.and also make sure that if it is called in
            // the stopped state, it returns w/o blocking.
            synchronized (bufferQ)
            {
                stopped = true;
                bufferQ.notifyAll();
            }
        }

        public void transferData(PushBufferStream pbs)
        {
            // Retrieve an empty buffer for the PSS to write into.
            Buffer buffer;
            synchronized (bufferQ)
            {
                // damencho (!stopped)
                while (!bufferQ.canWrite() && !closed && !stopped)
                {
                    try
                    {
                        bufferQ.wait();
                    } catch (Exception e)
                    {
                    }
                }
                // If source is null, the data source has been closed.
                if (closed || stopped)
                    return;
                buffer = bufferQ.getEmptyBuffer();
            }

            try
            {
                pbs.read(buffer);
            } catch (IOException e)
            {
                buffer.setDiscard(true);
            }

            // Until we find the first key frame, we won't
            // add it to the buffer queue.
            if (!keyFrameFound && !findKeyFrame(buffer))
            {
                synchronized (bufferQ)
                {
                    // Discard that buffer.
                    bufferQ.writeReport();
                    bufferQ.read();
                    bufferQ.readReport();
                }
                return;
            }

            // Put the filled buffer back to the queue for consumption.
            synchronized (bufferQ)
            {
                bufferQ.writeReport();
                bufferQ.notifyAll();
            }
        }
    }

    static final String NAMEBUFFER = "Raw video/audio buffer stream parser";

    private boolean started = false;
    // For comparing formats.
    static AudioFormat mpegAudio = new AudioFormat(AudioFormat.MPEG_RTP);
    static VideoFormat mpegVideo = new VideoFormat(VideoFormat.MPEG_RTP);
    static VideoFormat jpegVideo = new VideoFormat(VideoFormat.JPEG_RTP);
    static VideoFormat h261Video = new VideoFormat(VideoFormat.H261_RTP);

    static VideoFormat h263Video = new VideoFormat(VideoFormat.H263_RTP);
    // Seb
    // static VideoFormat h263_1998Video = new
    // VideoFormat(VideoFormat.H263_1998_RTP);

    /****************************************************************
     * Track class
     ****************************************************************/

    final int[] h261Widths = { 176, 352 };

    final int[] h261Heights = { 144, 288 };

    final int[] h263Widths = { 0, 128, 176, 352, 704, 1408, 0, 0 };

    final int[] h263Heights = { 0, 96, 144, 288, 576, 1152, 0, 0 };

    final float MPEGRateTbl[] = { 0.0f, 23.976f, 24.f, 25.f, 29.97f, 30.f,
            50.f, 59.94f, 60.f };

    public static int[][] MPASampleTbl = { { 22050, 24000, 16000, 0 }, // MPEG 2
            { 44100, 48000, 32000, 0 } // MPEG 1
    };

    @Override
    public void close()
    {
        if (source != null)
        {
            try
            {
                source.stop();
                // stop every tracks, so that readFrame() can be released.
                // close every tracks to unblock the transfer handlers.
                for (int i = 0; i < tracks.length; i++)
                {
                    ((FrameTrack) tracks[i]).stop();
                    ((FrameTrack) tracks[i]).close();
                }

                source.disconnect();
            } catch (Exception e)
            {
                // Internal error?
            }
            source = null;
        }
        started = false;
    }

    @Override
    public String getName()
    {
        return NAMEBUFFER;
    }

    /**
     */
    @Override
    public Track[] getTracks()
    {
        for (int i = 0; i < tracks.length; i++)
            ((FrameTrack) tracks[i]).parse();
        return tracks;
    }

    boolean isRTPFormat(Format fmt)
    {
        return fmt != null
                && fmt.getEncoding() != null
                && (fmt.getEncoding().endsWith("rtp") || fmt.getEncoding()
                        .endsWith("RTP"));
    }

    /**
     * Opens the plug-in software or hardware component and acquires necessary
     * resources. If all the needed resources could not be acquired, it throws a
     * ResourceUnavailableException. Data should not be passed into the plug-in
     * without first calling this method.
     */
    @Override
    public void open()
    {
        if (tracks != null)
            return;
        tracks = new Track[streams.length];
        for (int i = 0; i < streams.length; i++)
        {
            tracks[i] = new FrameTrack(this, (PushBufferStream) streams[i], 1);
        }
    }

    /**
     * Resets the state of the plug-in. Typically at end of media or when media
     * is repositioned.
     */
    @Override
    public void reset()
    {
        for (int i = 0; i < tracks.length; i++)
            ((FrameTrack) tracks[i]).reset();
    }

    @Override
    public void setSource(DataSource source) throws IOException,
            IncompatibleSourceException
    {
        SourceStream[] streams;

        if (!(source instanceof PushBufferDataSource))
        {
            throw new IncompatibleSourceException(
                    "DataSource not supported: " + source);
        } else
        {
            streams = ((PushBufferDataSource) source).getStreams();
        }

        if (streams == null)
        {
            throw new IOException("Got a null stream from the DataSource");
        }

        if (streams.length == 0)
        {
            throw new IOException(
                    "Got a empty stream array from the DataSource");
        }

        if (!supports(streams))
        {
            throw new IncompatibleSourceException(
                    "DataSource not supported: " + source);
        }

        this.source = source;
        this.streams = streams;
    }

    /**
     * Start the parser.
     */
    @Override
    public void start() throws IOException
    {
        if (started)
            return;
        for (int i = 0; i < tracks.length; i++)
            ((FrameTrack) tracks[i]).start();
        source.start();
        started = true;
    }

    /**
     * Stop the parser.
     */
    @Override
    public void stop()
    {
        try
        {
            // stop each of the tracks, so that readFrame can be released
            for (int i = 0; i < tracks.length; i++)
                ((FrameTrack) tracks[i]).stop();

            source.stop();

        } catch (Exception e)
        {
            // Internal errors?
        }
        started = false;
    }

    /**
     * Override this if the Parser has additional requirements from the
     * PushSourceStream
     */
    @Override
    protected boolean supports(SourceStream[] streams)
    {
        return ((streams[0] != null) && (streams[0] instanceof PushBufferStream));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy