CONTENTS | PREV | NEXT | INDEX JMF 2.0 API Guide



C

Demultiplexer Plug-In

This sample demonstrates how to implement a Demultiplexer plug-in to extract individual tracks from a media file. This example processes GSM files.

Example C-1: GSM Demultiplexer plug-in. (1 of 13)
 import java.io.IOException;
 import javax.media.*;
 import javax.media.protocol.*;
 import javax.media.format.Format;
 import javax.media.format.audio.AudioFormat;
 
 /**
  * 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.
  */
 
 public class SampleDeMux implements Demultiplexer {
     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;
         this.streams = streams;
         
         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("SampleDeMux: readBytes():
                              Reached end of stream while trying to read " + 
                              numBytes + " bytes");
                 } else {
                     return offset;
                 }
             } else if (actualRead == 
             com.sun.media.protocol.BasicSourceStream.LENGTH_DISCARD) {
                 return 
                   com.sun.media.protocol.BasicSourceStream.LENGTH_DISCARD;
             } else if (actualRead < 0) {
                 throw new IOException("SampleDeMux: 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 SampleDeMux parser;
         private long sequenceNumber = 0;
         private TrackListener listener;
         private long seekLocation = -1L;
         private long mediaSizeAtEOM = -1L; // update when EOM 
                                           // implied by IOException occurs
 
         BasicTrack(SampleDeMux 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(SampleDeMux 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 ==
                com.sun.media.protocol.BasicSourceStream.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(SampleDeMux.this, 
                   format, enabled, SampleDeMux.this.duration,
                   startTime, numBuffers, bufferSize,
                   SampleDeMux.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);
         }
     }
 }



CONTENTS | PREV | NEXT | INDEX

Copyright © 1998-1999 Sun Microsystems, Inc. All Rights Reserved.