Package net.sf.fmj.media.parser

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

package net.sf.fmj.media.parser;

import java.io.IOException;

import javax.media.BadHeaderException;
import javax.media.Buffer;
import javax.media.Demultiplexer;
import javax.media.Duration;
import javax.media.Format;
import javax.media.IncompatibleSourceException;
import javax.media.Time;
import javax.media.Track;
import javax.media.TrackListener;
import javax.media.format.AudioFormat;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.DataSource;
import javax.media.protocol.Positionable;
import javax.media.protocol.PullDataSource;
import javax.media.protocol.PullSourceStream;
import javax.media.protocol.Seekable;
import javax.media.protocol.SourceStream;

/**
* Demultiplexer for GSM file format
*/

/**
* GSM
* 8000 samples per sec.
* 160 samples represent 20 milliseconds and GSM represents them
* in 33 bytes. So frameSize is 33 bytes and there are 50 frames
* in one second. One second is 1650 bytes.
*/
/**
* Adapted from http://java.sun.com/products/java-media/jmf/2.1.1/guide/JMFApp-Demux.html#87606
*
*
*/
public class GsmParser implements Demultiplexer {
 
  private static final int LENGTH_DISCARD = -2;
    private Time duration = Duration.DURATION_UNKNOWN;
    private Format format = null;
    private Track[] tracks = new Track[1]; // Only 1 track is there for Gsm
    private int numBuffers = 4;
    private int bufferSize;
    private int dataSize;
    private int encoding;
    private String encodingString;
    private int sampleRate;
    private int samplesPerBlock;
    private int bytesPerSecond = 1650; // 33 * 50
    private int blockSize = 33;

    private int maxFrame = Integer.MAX_VALUE;
    private long minLocation;
    private long maxLocation;
    private PullSourceStream stream = null;
    private long currentLocation = 0;

    protected DataSource source;
    protected SourceStream[] streams;
    protected boolean seekable = false;
    protected boolean positionable = false;
    private Object sync = new Object(); // synchronizing variable

    private static ContentDescriptor[] supportedFormat =
        new ContentDescriptor[] {new ContentDescriptor("audio.x_gsm")};

    public ContentDescriptor [] getSupportedInputContentDescriptors() {
        return supportedFormat;
    }

    public void setSource(DataSource source)
        throws IOException, IncompatibleSourceException {

        if (!(source instanceof PullDataSource)) {
            throw new IncompatibleSourceException("DataSource not supported: " + source);
        } else {
            streams = ((PullDataSource) 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");
        }
        this.source = source;
       
        positionable =  (streams[0] instanceof Seekable);
        seekable =  positionable && ((Seekable)
                   streams[0]).isRandomAccess();

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

    /**
     * A Demultiplexer may support pull only or push only or both
     * pull and push streams.

     * Some Demultiplexer may have other requirements.
     * For e.g a quicktime Demultiplexer imposes an additional
     * requirement that
     * isSeekable() and isRandomAccess() be true
     */
    protected boolean supports(SourceStream[] streams) {
        return ( (streams[0] != null) &&
                 (streams[0] instanceof PullSourceStream) );   
    }

    public boolean isPositionable() {
        return positionable;
    }

    public boolean isRandomAccess() {
        return seekable;
    }

    /**
     * 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.
     */
    public void open() {
        // throws ResourceUnavailableException;
    }

    /**
     * Closes the plug-in component and releases resources. No more data
     * will be accepted by the plug-in after a call to this method. The
     * plug-in can be reinstated after being closed by calling
     * <code>open</code>.
     */
    public void close() {
        if (source != null) {
            try {
                source.stop();
                source.disconnect();
            } catch (IOException e) {
                // Internal error?
            }
            source = null;
        }
    }

    /**
     * This get called when the player/processor is started.
     */
    public void start() throws IOException {
         if (source != null)
             source.start();
    }

    /**
     * This get called when the player/processor is stopped.
     */
    public void stop() {
        if (source != null) {
            try {
                source.stop();
            } catch (IOException e) {
                // Internal errors?
            }
        }
    }

    /**
     * Resets the state of the plug-in. Typically at end of media
     * or when media is repositioned.
     */
    public void reset() {
    }

    public Track[] getTracks() throws IOException, BadHeaderException {

        if (tracks[0] != null)
            return tracks;       
        stream = (PullSourceStream) streams[0];
        readHeader();
        bufferSize = bytesPerSecond;
        tracks[0] = new GsmTrack((AudioFormat) format,
                                /*enabled=*/ true,
                                 new Time(0),
                                 numBuffers,
                                 bufferSize,
                                 minLocation,
                                 maxLocation
                                 );
        return tracks;
    }

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

    public Object getControl(String controlType) {
        return null;
    }
    private void /* for now void */ readHeader()
        throws IOException, BadHeaderException {

        minLocation = getLocation(stream); // Should be zero

        long contentLength = stream.getContentLength();
        if ( contentLength != SourceStream.LENGTH_UNKNOWN ) {
            double durationSeconds = contentLength / bytesPerSecond;


            duration = new Time(durationSeconds);
            maxLocation = contentLength;

        } else {
            maxLocation = Long.MAX_VALUE;
        }

        boolean signed = true;
        boolean bigEndian = false;
        format = new AudioFormat(AudioFormat.GSM,
                                 8000// sampleRate,
                                 16,    // sampleSizeInBits,
                                 1,     // channels,
                                 bigEndian ? AudioFormat.BIG_ENDIAN :
                                AudioFormat.LITTLE_ENDIAN,
                                 signed ? AudioFormat.SIGNED :
                                AudioFormat.UNSIGNED,
                                 (blockSize * 8), // frameSizeInBits
                                 Format.NOT_SPECIFIED, 
                                 Format.byteArray);
    }

    // Contains 1 audio track
    public String getTrackLayout() {
        return "A";
    }

    public Time setPosition(Time where, int rounding) {
        if (! seekable ) {
            return getMediaTime();
        }

        long time = where.getNanoseconds();
        long newPos;

        if (time < 0)
            time = 0;

        double newPosd = time * bytesPerSecond / 1000000000.0;
        double remainder = (newPosd % blockSize);
       
        newPos = (long) (newPosd - remainder);

        if (remainder > 0) {
            switch (rounding) {
            case Positionable.RoundUp:
                newPos += blockSize;
                break;
            case Positionable.RoundNearest:
                if (remainder > (blockSize / 2.0))
                    newPos += blockSize;

                break;
            }
        }

        if ( newPos > maxLocation )
            newPos = maxLocation;
       
        newPos += minLocation;
        ((BasicTrack) tracks[0]).setSeekLocation(newPos);
        return where;
    }

    public Time getMediaTime() {
        long location;
        long seekLocation = ((BasicTrack) tracks[0]).getSeekLocation();
        if (seekLocation != -1)
            location = seekLocation - minLocation;
        else
            location = getLocation(stream) - minLocation;

        return new Time( location / (double) bytesPerSecond );
    }

    public Time getDuration() {
        if ( duration.equals(Duration.DURATION_UNKNOWN) &&
             ( tracks[0] != null ) ) {
            long mediaSizeAtEOM = ((BasicTrack)
                                 tracks[0]).getMediaSizeAtEOM();
            if (mediaSizeAtEOM > 0) {
                double durationSeconds = mediaSizeAtEOM / bytesPerSecond;
               duration = new Time(durationSeconds);
            }
        }
        return duration;
    }

    /**
     * Returns a descriptive name for the plug-in.
     * This is a user readable string.
     */
    public String getName() {
        return "Parser for raw GSM";
    }

    /**
     * Read numBytes from offset 0
     */
    public int readBytes(PullSourceStream pss, byte[] array,
                         int numBytes) throws IOException {

        return readBytes(pss, array, 0, numBytes);
    }

    public int readBytes(PullSourceStream pss, byte[] array,
                         int offset,
                         int numBytes) throws IOException {
        if (array == null) {
            throw new NullPointerException();
        } else if ((offset < 0) || (offset > array.length) || 
                  (numBytes < 0) ||
                   ((offset + numBytes) > array.length) ||
                  ((offset + numBytes) < 0)) {
            throw new IndexOutOfBoundsException();
        } else if (numBytes == 0) {
            return 0;
        }

        int remainingLength = numBytes;
        int actualRead = 0;

        remainingLength = numBytes;
        while (remainingLength > 0) {

            actualRead = pss.read(array, offset, remainingLength);
            if (actualRead == -1) {// End of stream
                if (offset == 0) {
                    throw new IOException("GsmParser: readBytes(): Reached end of stream while trying to read " +
                             numBytes + " bytes");
                } else {
                    return offset;
                }
            } else if (actualRead ==
            LENGTH_DISCARD) {
                return
                  LENGTH_DISCARD;
            } else if (actualRead < 0) {
                throw new IOException("GsmParser: readBytes() read returned " + actualRead);
            }
            remainingLength -= actualRead;
            offset += actualRead;
            synchronized(sync) {
                currentLocation += actualRead;
            }
        }
        return numBytes;
    }

    protected final long getLocation(PullSourceStream pss) {
        synchronized(sync) {
            if ( (pss instanceof Seekable) )
                return ((Seekable)pss).tell();

            else
                return currentLocation;
        }
    }

    ////////////////////////////////////////////////////////////
    // Inner classes begin
    abstract private class BasicTrack implements Track {

        private Format format;
        private boolean enabled = true;
        protected Time duration;
        private Time startTime;
        private int numBuffers;
        private int dataSize;
        private PullSourceStream stream;
        private long minLocation;
        private long maxLocation;
        private long maxStartLocation;
        private GsmParser parser;
        private long sequenceNumber = 0;
        private TrackListener listener;
        private long seekLocation = -1L;
        private long mediaSizeAtEOM = -1L; // update when EOM
                                          // implied by IOException occurs

        BasicTrack(GsmParser parser,
                   Format format, boolean enabled,
                  Time duration, Time startTime,
                   int numBuffers, int dataSize,
                  PullSourceStream stream) {
            this(parser, format,  enabled,  duration,  startTime,
                 numBuffers, dataSize, stream,
                 0L, Long.MAX_VALUE);
        }


        /**
         * Note to implementors who want to use this class.
         * If the maxLocation is not known, then
         * specify Long.MAX_VALUE for this parameter
         */
        public BasicTrack(GsmParser parser,
                          Format format, boolean enabled,
                         Time duration, Time startTime,
                          int numBuffers, int dataSize,
                         PullSourceStream stream,
                          long minLocation, long maxLocation) {

            this.parser = parser;

            this.format = format;
            this.enabled = enabled;
            this.duration = duration;
            this.startTime = startTime;
            this.numBuffers = numBuffers;
            this.dataSize = dataSize;
            this.stream = stream;
            this.minLocation = minLocation;
            this.maxLocation = maxLocation;
            maxStartLocation = maxLocation - dataSize;
        }

        public Format getFormat() {
            return format;
        }

        public void setEnabled(boolean t) {
            enabled = t;
        }

        public boolean isEnabled() {
            return enabled;
        }

        public Time getDuration() {
            return duration;
        }


        public Time getStartTime() {
            return startTime;
        }


        public int getNumberOfBuffers() {
            return numBuffers;
        }


        public void setTrackListener(TrackListener l) {
            listener = l;
        }
   
        public synchronized void setSeekLocation(long location) {
            seekLocation = location;
        }
        public synchronized long getSeekLocation() {
            return seekLocation;
        }

        public void readFrame(Buffer buffer) {
            if (buffer == null)
                return;

            if (!enabled) {
                buffer.setDiscard(true);
                return;
            }

            buffer.setFormat(format);
            Object obj = buffer.getData();
            byte[] data;
            long location;
            boolean needToSeek;
       
            synchronized(this) {
                if (seekLocation != -1) {
                    location = seekLocation;
                    seekLocation = -1;
                    needToSeek = true;
                } else {
                    location = parser.getLocation(stream);
                    needToSeek = false;
                }
            }

            int needDataSize;

            if (location < minLocation) {
                buffer.setDiscard(true);
                return;
            } else if (location >= maxLocation) {
                buffer.setLength(0);
                buffer.setEOM(true);
                return;
            } else if (location > maxStartLocation) {
                needDataSize = dataSize - (int) (location -
                              maxStartLocation);
            } else {
                needDataSize = dataSize;
            }

            if  ( (obj == null) ||
                  (! (obj instanceof byte[]) ) ||
                  ( ((byte[])obj).length < needDataSize) ) {
                data = new byte[needDataSize];
                buffer.setData(data);
            } else {
                data = (byte[]) obj;
            }
            try {
                if (needToSeek) {
                    long pos =
               ((javax.media.protocol.Seekable)stream).seek(location);

                    if ( pos == LENGTH_DISCARD) {
                        buffer.setDiscard(true);
                        return;
                    }
                }
                int actualBytesRead = parser.readBytes(stream,
                                     data, needDataSize);
                buffer.setOffset(0);
                buffer.setLength(actualBytesRead);
                buffer.setSequenceNumber(++sequenceNumber);
              buffer.setTimeStamp(parser.getMediaTime().getNanoseconds());
            } catch (IOException e) {
                if (maxLocation != Long.MAX_VALUE) {
                    // Known maxLocation. So, this is a case of
                    // deliberately reading past EOM
                    System.err.println("readFrame: EOM " + e);
                    buffer.setLength(0); // Need this??
                    buffer.setEOM(true);
                } else {
                    // Unknown maxLocation, due to unknown content length
                    // EOM reached before the required bytes could be read.
                    long length = parser.streams[0].getContentLength();
                    if ( length != SourceStream.LENGTH_UNKNOWN ) {
                        // If content-length is known, discard this buffer,
                    // updatemaxLocation, maxStartLocation and
                    // mediaSizeAtEOM.  The next readFrame will read
                   // the remaining data till EOM.
                        maxLocation = length;
                        maxStartLocation = maxLocation - dataSize;
                        mediaSizeAtEOM = maxLocation - minLocation;
                        buffer.setLength(0); // Need this??
                        buffer.setDiscard(true);
                    } else {
                        // Content Length is still unknown after an
                       // IOException.
                        // We can still discard this buffer and keep discarding
                        // until content length is known. But this may go into
                        // into an infinite loop, if there are real IO errors
                        // So, return EOM
                        maxLocation = parser.getLocation(stream);
                        maxStartLocation = maxLocation - dataSize;
                        mediaSizeAtEOM = maxLocation - minLocation;
                        buffer.setLength(0); // Need this??
                        buffer.setEOM(true);
                    }
                }
            }
        }

        public void readKeyFrame(Buffer buffer) {
            readFrame(buffer);
        }

        public boolean willReadFrameBlock() {
            return false;
        }


        public long getMediaSizeAtEOM() {
            return mediaSizeAtEOM; // updated when EOM implied by                    
                             // IOException occurs
        }
    }

    private class GsmTrack extends BasicTrack {
        private double sampleRate;
        private float timePerFrame = 0.020F; // 20 milliseconds

        GsmTrack(AudioFormat format, boolean enabled, Time startTime,
                 int numBuffers, int bufferSize,
                 long minLocation, long maxLocation) {
            super(GsmParser.this,
                  format, enabled, GsmParser.this.duration,
                  startTime, numBuffers, bufferSize,
                  GsmParser.this.stream, minLocation, maxLocation);

            double sampleRate = format.getSampleRate();
            int channels = format.getChannels();
            int sampleSizeInBits = format.getSampleSizeInBits();

            float bytesPerSecond;
            float bytesPerFrame;
            float samplesPerFrame;

            long durationNano = this.duration.getNanoseconds();
            if (!( (durationNano ==
                  Duration.DURATION_UNKNOWN.getNanoseconds()) ||
                   (durationNano ==
                   Duration.DURATION_UNBOUNDED.getNanoseconds()) )) {
                maxFrame = mapTimeToFrame(this.duration.getSeconds());
            }
        }

        GsmTrack(AudioFormat format, boolean enabled, Time startTime,
                 int numBuffers, int bufferSize) {
            this(format, enabled,
                 startTime, numBuffers, bufferSize,
                 0L, Long.MAX_VALUE);

        }

        // Frame numbers start from 0
        private int mapTimeToFrame(double time) {
            double frameNumber = time / timePerFrame;
            return (int) frameNumber;
        }

        // Frame numbers start from 0
        // 0-1 ==> 0, 1-2 ==> 1
        public int mapTimeToFrame(Time t) {
            double time = t.getSeconds();
            int frameNumber = mapTimeToFrame(time);
           
            if ( frameNumber > maxFrame)
                frameNumber = maxFrame; // Do we clamp it or return error
            System.out.println("mapTimeToFrame: " + (int) time + " ==> " +
                               frameNumber + " ( " + frameNumber + " )");
            return frameNumber;
        }
        public Time mapFrameToTime(int frameNumber) {
            if (frameNumber > maxFrame)
                frameNumber = maxFrame; // Do we clamp it or return error
            double time = timePerFrame * frameNumber;
            System.out.println("mapFrameToTime: " + frameNumber + " ==> " +
                                   time);
            return new Time(time);
        }
    }
}

TOP

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

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.