/*
* @(#)BasicPullParser.java 1.20 02/08/21
*
* Copyright (c) 1996-2002 Sun Microsystems, Inc. All rights reserved.
*/
package com.sun.media.parser;
import java.io.IOException;
import javax.media.Demultiplexer;
import javax.media.IncompatibleSourceException;
import javax.media.protocol.DataSource;
import javax.media.protocol.PushDataSource;
import javax.media.protocol.PullDataSource;
import javax.media.protocol.SourceStream;
import javax.media.protocol.PullSourceStream;
import javax.media.protocol.Positionable;
import javax.media.protocol.Seekable;
import javax.media.protocol.CachedStream;
import javax.media.protocol.ContentDescriptor;
import javax.media.Format;
import com.sun.media.BasicPlugIn;
public abstract class BasicPullParser extends BasicPlugIn implements Demultiplexer {
protected DataSource source;
protected SourceStream[] streams;
private Format[] outputFormats;
private byte[] b = new byte[1];
private byte[] intArray = new byte[4];
private byte[] shortArray = new byte[2];
private final int TEMP_BUFFER_LENGTH = 2048;
private byte[] tempBuffer = new byte[TEMP_BUFFER_LENGTH];
private long currentLocation = 0;
protected boolean seekable = false;
protected boolean positionable = false;
protected CachedStream cacheStream;
private Object sync = new Object(); // synchronizing variable
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);
try {
cacheStream = (CachedStream) streams[0];
} catch (ClassCastException e) {
// System.out.println("bpparser: cacheStream is null");
cacheStream = null;
}
}
/**
* A parser may support pull only or push only or both
* pull and push streams.
* Some parsers may have other requirements.
* A quicktime parser imposes an additional requirement that
* isSeekable() and isRandomAccess() be true
*
* Override this if the Parser has additional requirements
* from the PullSourceStream
*/
protected boolean supports(SourceStream[] streams) {
return ( (streams[0] != null) &&
(streams[0] instanceof PullSourceStream) );
}
public boolean isPositionable() {
return positionable;
}
// multiple streams??
// public boolean isSeekable() {
// return (streams[0] instanceof Seekable);
// }
public boolean isRandomAccess() {
return seekable;
}
/**
* Read numBytes from offset 0
*/
public int readBytes(PullSourceStream pss, byte[] array,
int numBytes) throws IOException {
return readBytes(pss, array, 0, numBytes);
}
// TODO: when working on the quicktime parser, you can
// decide whether to remove the PullSourceStream arg. from
// the read and skip methods. Use streams[0]
public int readBytes(PullSourceStream pss, byte[] array,
int offset,
int numBytes) throws IOException {
//TODO synchronized(array) {
// Is this if check too much overhead?
// Can we rely on parsers to call this method with valid args?
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) {
// Note: Using this as we don't have EndOfMediaException
throw new IOException("BasicPullParser: readBytes(): Reached end of stream while trying to read " + numBytes + " bytes");
} else {
// System.out.println("readBytes: ASKED for " + numBytes +
// " GOT " + offset +
// "NEXT read will be EOM");
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("BasicPullParser: readBytes() read returned " + actualRead);
}
remainingLength -= actualRead;
// System.out.println(" remainingLength is " + remainingLength);
offset += actualRead;
synchronized(sync) {
currentLocation += actualRead;
}
}
return numBytes;
// System.out.println("Finished reading " + numBytes);
//TODO }
}
public /*protected*/ int readInt(PullSourceStream pss) throws IOException {
return readInt(pss, true);
}
public /*protected*/ int readShort(PullSourceStream pss) throws IOException {
return readShort(pss, true);
}
public int readByte(PullSourceStream pss) throws IOException {
readBytes(pss, b, 1);
return (int) b[0];
}
protected int readInt(PullSourceStream pss,
boolean isBigEndian) throws IOException {
int result;
readBytes(pss, intArray, 4);
if (isBigEndian) {
result = ((intArray[0] & 0xFF) << 24) |
((intArray[1] & 0xFF) << 16) |
((intArray[2] & 0xFF) << 8) |
(intArray[3] & 0xFF);
} else {
result = ((intArray[3] & 0xFF) << 24) |
((intArray[2] & 0xFF) << 16) |
((intArray[1] & 0xFF) << 8) |
(intArray[0] & 0xFF);
}
return result;
}
protected int parseIntFromArray(byte[] array, int offset,
boolean isBigEndian) throws IOException {
int result;
if (isBigEndian) {
result = ((array[offset+0] & 0xFF) << 24) |
((array[offset+1] & 0xFF) << 16) |
((array[offset+2] & 0xFF) << 8) |
(array[offset+3] & 0xFF);
} else {
result = ((array[offset+3] & 0xFF) << 24) |
((array[offset+2] & 0xFF) << 16) |
((array[offset+1] & 0xFF) << 8) |
(array[offset+0] & 0xFF);
}
return result;
}
// Note: can use parseShortFromArray method except that
// there is the overhead of the if call
protected short readShort(PullSourceStream pss,
boolean isBigEndian) throws IOException {
int result;
readBytes(pss, shortArray, 2);
if (isBigEndian) {
result = ((shortArray[0] &0xFF) << 8) |
(shortArray[1] &0xFF);
} else {
result = ((shortArray[1] &0xFF) << 8) |
(shortArray[0] & 0xFF);
}
return (short) result;
}
public final static short parseShortFromArray(byte[] array, boolean isBigEndian)
throws IOException {
if (array.length < 2)
throw new IOException("Unexpected EOF");
int result;
if (isBigEndian) {
result = ((array[0] &0xFF) << 8) |
(array[1] &0xFF);
} else {
result = ((array[1] &0xFF) << 8) |
(array[0] & 0xFF);
}
return (short) result;
}
protected String readString(PullSourceStream pss) throws IOException {
readBytes(pss, intArray, 4);
return new String(intArray);
}
// TODO: REPEAT: when working on the quicktime parser, you can
// decide whether to remove the PullSourceStream arg. from
// the read and skip and getLocation methods. Use streams[0]
// TODO: use the boolean seekable
public /*protected*/ void skip(PullSourceStream pss, int numBytes) throws IOException {
// System.out.println("skip : " + numBytes);
// System.out.println("seekable is " + (pss instanceof Seekable));
if ( (pss instanceof Seekable) && ((Seekable)pss).isRandomAccess() ) {
long current = ((Seekable)pss).tell();
long newPos = current + numBytes;
// System.out.println("bpp:skip " + numBytes + " : " + current + " : " + newPos);
((Seekable)pss).seek( newPos );
if (newPos != ((Seekable)pss).tell()) {
// System.out.println("DEBUG: seek to " + newPos + " failed");
// Is this the correct thing to do?
throw new IOException("Seek to " + newPos + " failed");
}
synchronized(sync) {
currentLocation += numBytes;
}
return;
}
// NOTE: readBytes with null as second arg. should do the job.
// But there is a bug in the PullSourceStream implementation
// readBytes(pss, null, numBytes);
int remaining = numBytes;
int bytesRead;
while (remaining > TEMP_BUFFER_LENGTH) {
// System.out.println("Calling readBytes " + TEMP_BUFFER_LENGTH);
bytesRead = readBytes(pss, tempBuffer, TEMP_BUFFER_LENGTH);
if (bytesRead != TEMP_BUFFER_LENGTH) {
throw new IOException("BasicPullParser: End of Media reached while trying to skip " + numBytes);
}
remaining -= TEMP_BUFFER_LENGTH;
}
if (remaining > 0) {
// System.out.println("Calling readBytes " + remaining);
bytesRead = readBytes(pss, tempBuffer, remaining);
if (bytesRead != remaining) {
throw new IOException("BasicPullParser: End of Media reached while trying to skip " + numBytes);
}
}
synchronized(sync) {
currentLocation += numBytes;
}
}
// TODO: use the boolean seekable
public /*protected*/ final long getLocation(PullSourceStream pss) {
synchronized(sync) {
if ( (pss instanceof Seekable) )
return ((Seekable)pss).tell();
else
return currentLocation;
}
}
/**
* Lists the possible input formats supported by this plug-in.
*/
public abstract ContentDescriptor [] getSupportedInputContentDescriptors();
/**
* 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() {
}
}
|