/* * $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 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(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 continued packet */ public boolean isContinued() { return continued; } /** * @return true 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; } }