/* * @(#)GsmParser.java 1.14 02/08/21 * * Copyright (c) 1996-2002 Sun Microsystems, Inc. All rights reserved. */ package com.sun.media.parser.audio; import java.io.IOException; import javax.media.Time; import javax.media.*; import javax.media.Duration; import javax.media.Track; import javax.media.BadHeaderException; import javax.media.protocol.SourceStream; import javax.media.protocol.PullSourceStream; import javax.media.protocol.Positionable; import javax.media.Format; import javax.media.protocol.ContentDescriptor; import javax.media.format.AudioFormat; import com.sun.media.parser.BasicPullParser; import com.sun.media.parser.BasicTrack; import com.sun.media.util.SettableTime; public class GsmParser extends BasicPullParser { private Time duration = Duration.DURATION_UNKNOWN; private Format format = null; private Track[] tracks = new Track[1]; // Only 1 track is there for wave private int numBuffers = 4; // TODO: check private int bufferSize; private int dataSize; private SettableTime mediaTime = new SettableTime(0L); private int encoding; private String encodingString; private int sampleRate; private int samplesPerBlock; private int bytesPerSecond = 1650; // 33 * 50 private int blockSize = 33; private long minLocation; private long maxLocation; private PullSourceStream stream = null; private static ContentDescriptor[] supportedFormat = new ContentDescriptor[] {new ContentDescriptor("audio.x_gsm")}; public ContentDescriptor [] getSupportedInputContentDescriptors() { return supportedFormat; } public Track[] getTracks() throws IOException, BadHeaderException { if (tracks[0] != null) return tracks; stream = (PullSourceStream) streams[0]; // Since the readHeader doesn't read anything there // is no need to disable buffering readHeader(); bufferSize = bytesPerSecond; tracks[0] = new GsmTrack((AudioFormat) format, /*enabled=*/ true, new Time(0), numBuffers, bufferSize, minLocation, maxLocation ); return tracks; } /** * 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. */ 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, // No FRAME_RATE specified Format.byteArray); } // TODO: Should reset sequence number after a setPosition // TODO: Optimize 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); if (cacheStream != null) { synchronized(this) { // cacheStream.setPosition(where.getNanoseconds()); cacheStream.abortRead(); } } return where; // TODO: return the actual time value } // Can be moved to base class. au parser also uses same code. public Time getMediaTime() { long location; long seekLocation = ((BasicTrack) tracks[0]).getSeekLocation(); if (seekLocation != -1) location = seekLocation - minLocation; else location = getLocation(stream) - minLocation; synchronized(mediaTime) { mediaTime.set( location / (double) bytesPerSecond ); } return mediaTime; } 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"; } class GsmTrack extends BasicTrack { private double sampleRate; private float timePerFrame = 0.020F; // 20 milliseconds private SettableTime frameToTime = new SettableTime(); 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(); } GsmTrack(AudioFormat format, boolean enabled, Time startTime, int numBuffers, int bufferSize) { this(format, enabled, startTime, numBuffers, bufferSize, 0L, Long.MAX_VALUE); } } }