/* * $ProjectName$ * $ProjectRevision$ * ----------------------------------------------------------- * $Id$ * ----------------------------------------------------------- * * $Author$ * * Description: * * Copyright 2002-2003 Tor-Einar Jarnbjo * ----------------------------------------------------------- * * Change History * ----------------------------------------------------------- * $Log$ * Revision 1.1 2005/07/11 15:42:36 hcl * Songdb java version, source. only 1.5 compatible * * Revision 1.1.1.1 2004/04/04 22:09:12 shred * First Import * * Revision 1.3 2003/04/10 19:48:22 jarnbjo * no message * * Revision 1.2 2003/03/31 00:23:04 jarnbjo * no message * * Revision 1.1 2003/03/03 21:02:20 jarnbjo * no message * */ package de.jarnbjo.ogg; import java.io.*; import de.jarnbjo.util.io.*; /** *
An instance of this class represents an ogg page read from an ogg file
* or network stream. It has no public constructor, but instances can be
* created by the create
methods, supplying a JMF stream or
* a RandomAccessFile
* which is positioned at the beginning of an Ogg page.
Furtheron, the class provides methods for accessing the raw page data, * as well as data attributes like segmenting information, sequence number, * stream serial number, chechsum and wether this page is the beginning or * end of a logical bitstream (BOS, EOS) and if the page data starts with a * continued packet or a fresh data packet.
*/ public class OggPage { private int version; private boolean continued, bos, eos; private long absoluteGranulePosition; private int streamSerialNumber, pageSequenceNumber, pageCheckSum; private int[] segmentOffsets; private int[] segmentLengths; private int totalLength; private byte[] header, segmentTable, data; protected OggPage() { } private OggPage( int version, boolean continued, boolean bos, boolean eos, long absoluteGranulePosition, int streamSerialNumber, int pageSequenceNumber, int pageCheckSum, int[] segmentOffsets, int[] segmentLengths, int totalLength, byte[] header, byte[] segmentTable, byte[] data) { this.version=version; this.continued=continued; this.bos=bos; this.eos=eos; this.absoluteGranulePosition=absoluteGranulePosition; this.streamSerialNumber=streamSerialNumber; this.pageSequenceNumber=pageSequenceNumber; this.pageCheckSum=pageCheckSum; this.segmentOffsets=segmentOffsets; this.segmentLengths=segmentLengths; this.totalLength=totalLength; this.header=header; this.segmentTable=segmentTable; this.data=data; } /** * this method equals to create(RandomAccessFile source, false) * * @see #create(RandomAccessFile, boolean) */ public static OggPage create(RandomAccessFile source) throws IOException, EndOfOggStreamException, OggFormatException { return create(source, false); } /** * This method is called to read data from the current position in the * specified RandomAccessFile and create a new OggPage instance based on the data * read. If the parameterskipData
is set to true
,
* the actual page segments (page data) is skipped and not read into
* memory. This mode is useful when scanning through an ogg file to build
* a seek table.
*
* @param source the source from which the ogg page is generated
* @param skipData if set to true
, the actual page data is not read into memory
* @return an ogg page created by reading data from the specified source, starting at the current position
* @throws FormatException if the data read from the specified source is not matching the specification for an ogg page
* @throws EndOfStreamException if it is not possible to read an entire ogg page from the specified source
* @throws IOException if some other I/O error is detected when reading from the source
*
* @see #create(RandomAccessFile)
*/
public static OggPage create(RandomAccessFile source, boolean skipData) throws IOException, EndOfOggStreamException, OggFormatException {
return create((Object)source, skipData);
}
/**
* this method equals to create(InputStream source, false)
*
* @see #create(InputStream, boolean)
*/
public static OggPage create(InputStream source) throws IOException, EndOfOggStreamException, OggFormatException {
return create(source, false);
}
/**
* This method is called to read data from the current position in the
* specified InpuStream and create a new OggPage instance based on the data
* read. If the parameter skipData
is set to true
,
* the actual page segments (page data) is skipped and not read into
* memory. This mode is useful when scanning through an ogg file to build
* a seek table.
*
* @param source the source from which the ogg page is generated
* @param skipData if set to true
, the actual page data is not read into memory
* @return an ogg page created by reading data from the specified source, starting at the current position
* @throws FormatException if the data read from the specified source is not matching the specification for an ogg page
* @throws EndOfStreamException if it is not possible to read an entire ogg page from the specified source
* @throws IOException if some other I/O error is detected when reading from the source
*
* @see #create(InputStream)
*/
public static OggPage create(InputStream source, boolean skipData) throws IOException, EndOfOggStreamException, OggFormatException {
return create((Object)source, skipData);
}
/**
* this method equals to create(byte[] source, false)
*
* @see #create(byte[], boolean)
*/
public static OggPage create(byte[] source) throws IOException, EndOfOggStreamException, OggFormatException {
return create(source, false);
}
/**
* This method is called to
* create a new OggPage instance based on the specified byte array.
*
* @param source the source from which the ogg page is generated
* @param skipData if set to true
, the actual page data is not read into memory
* @return an ogg page created by reading data from the specified source, starting at the current position
* @throws FormatException if the data read from the specified source is not matching the specification for an ogg page
* @throws EndOfStreamException if it is not possible to read an entire ogg page from the specified source
* @throws IOException if some other I/O error is detected when reading from the source
*
* @see #create(byte[])
*/
public static OggPage create(byte[] source, boolean skipData) throws IOException, EndOfOggStreamException, OggFormatException {
return create((Object)source, skipData);
}
private static OggPage create(Object source, boolean skipData) throws IOException, EndOfOggStreamException, OggFormatException {
try {
int sourceOffset=27;
byte[] header=new byte[27];
if(source instanceof RandomAccessFile) {
RandomAccessFile raf=(RandomAccessFile)source;
if(raf.getFilePointer()==raf.length()) {
return null;
}
raf.readFully(header);
}
else if(source instanceof InputStream) {
readFully((InputStream)source, header);
}
else if(source instanceof byte[]) {
System.arraycopy((byte[])source, 0, header, 0, 27);
}
BitInputStream bdSource=new ByteArrayBitInputStream(header);
int capture=bdSource.getInt(32);
if(capture!=0x5367674f) {
//throw new FormatException("Ogg page does not start with 'OggS' (0x4f676753)");
/*
** This condition is IMHO an error, but older Ogg files often contain
** pages with a different capture than OggS. I am not sure how to
** manage these pages, but the decoder seems to work properly, if
** the incorrect capture is simply ignored.
*/
String cs=Integer.toHexString(capture);
while(cs.length()<8) {
cs="0"+cs;
}
cs=cs.substring(6, 8)+cs.substring(4, 6)+cs.substring(2, 4)+cs.substring(0, 2);
char c1=(char)(Integer.valueOf(cs.substring(0, 2), 16).intValue());
char c2=(char)(Integer.valueOf(cs.substring(2, 4), 16).intValue());
char c3=(char)(Integer.valueOf(cs.substring(4, 6), 16).intValue());
char c4=(char)(Integer.valueOf(cs.substring(6, 8), 16).intValue());
System.out.println("Ogg packet header is 0x"+cs+" ("+c1+c2+c3+c4+"), should be 0x4f676753 (OggS)");
}
int version=bdSource.getInt(8);
byte tmp=(byte)bdSource.getInt(8);
boolean bf1=(tmp&1)!=0;
boolean bos=(tmp&2)!=0;
boolean eos=(tmp&4)!=0;
long absoluteGranulePosition=bdSource.getLong(64);
int streamSerialNumber=bdSource.getInt(32);
int pageSequenceNumber=bdSource.getInt(32);
int pageCheckSum=bdSource.getInt(32);
int pageSegments=bdSource.getInt(8);
//System.out.println("OggPage: "+streamSerialNumber+" / "+absoluteGranulePosition+" / "+pageSequenceNumber);
int[] segmentOffsets=new int[pageSegments];
int[] segmentLengths=new int[pageSegments];
int totalLength=0;
byte[] segmentTable=new byte[pageSegments];
byte[] tmpBuf=new byte[1];
for(int i=0; itrue
if this page begins with a fresh packet
*/
public boolean isFresh() {
return !continued;
}
/**
* @return true
if this page is the beginning of a logical stream
*/
public boolean isBos() {
return bos;
}
/**
* @return true
if this page is the end of a logical stream
*/
public boolean isEos() {
return eos;
}
}