From 9fee0ec4ca0c5b7a334cc29dbb58e76c7a4c736e Mon Sep 17 00:00:00 2001 From: Michiel Van Der Kolk Date: Mon, 11 Jul 2005 15:42:37 +0000 Subject: Songdb java version, source. only 1.5 compatible git-svn-id: svn://svn.rockbox.org/rockbox/trunk@7101 a1c6a512-1295-4272-9138-f99709370657 --- songdbj/de/jarnbjo/ogg/BasicStream.java | 121 ++++++ songdbj/de/jarnbjo/ogg/CachedUrlStream.java | 252 ++++++++++++ .../de/jarnbjo/ogg/EndOfOggStreamException.java | 45 +++ songdbj/de/jarnbjo/ogg/FileStream.java | 154 ++++++++ songdbj/de/jarnbjo/ogg/LogicalOggStream.java | 151 ++++++++ songdbj/de/jarnbjo/ogg/LogicalOggStreamImpl.java | 213 ++++++++++ songdbj/de/jarnbjo/ogg/OggFormatException.java | 50 +++ songdbj/de/jarnbjo/ogg/OggPage.java | 431 +++++++++++++++++++++ songdbj/de/jarnbjo/ogg/OnDemandUrlStream.java | 127 ++++++ songdbj/de/jarnbjo/ogg/PhysicalOggStream.java | 124 ++++++ songdbj/de/jarnbjo/ogg/UncachedUrlStream.java | 207 ++++++++++ .../util/audio/FadeableAudioInputStream.java | 62 +++ songdbj/de/jarnbjo/util/io/BitInputStream.java | 185 +++++++++ .../jarnbjo/util/io/ByteArrayBitInputStream.java | 352 +++++++++++++++++ songdbj/de/jarnbjo/util/io/HuffmanNode.java | 144 +++++++ songdbj/de/jarnbjo/vorbis/AudioPacket.java | 328 ++++++++++++++++ songdbj/de/jarnbjo/vorbis/CodeBook.java | 275 +++++++++++++ songdbj/de/jarnbjo/vorbis/CommentHeader.java | 244 ++++++++++++ songdbj/de/jarnbjo/vorbis/Floor.java | 124 ++++++ songdbj/de/jarnbjo/vorbis/Floor0.java | 74 ++++ songdbj/de/jarnbjo/vorbis/Floor1.java | 324 ++++++++++++++++ .../de/jarnbjo/vorbis/IdentificationHeader.java | 120 ++++++ songdbj/de/jarnbjo/vorbis/Mapping.java | 59 +++ songdbj/de/jarnbjo/vorbis/Mapping0.java | 146 +++++++ songdbj/de/jarnbjo/vorbis/MdctFloat.java | 321 +++++++++++++++ songdbj/de/jarnbjo/vorbis/Mode.java | 75 ++++ songdbj/de/jarnbjo/vorbis/Residue.java | 260 +++++++++++++ songdbj/de/jarnbjo/vorbis/Residue0.java | 53 +++ songdbj/de/jarnbjo/vorbis/Residue1.java | 55 +++ songdbj/de/jarnbjo/vorbis/Residue2.java | 123 ++++++ songdbj/de/jarnbjo/vorbis/SetupHeader.java | 131 +++++++ songdbj/de/jarnbjo/vorbis/Util.java | 127 ++++++ .../de/jarnbjo/vorbis/VorbisAudioFileReader.java | 217 +++++++++++ .../de/jarnbjo/vorbis/VorbisFormatException.java | 51 +++ songdbj/de/jarnbjo/vorbis/VorbisStream.java | 247 ++++++++++++ 35 files changed, 5972 insertions(+) create mode 100644 songdbj/de/jarnbjo/ogg/BasicStream.java create mode 100644 songdbj/de/jarnbjo/ogg/CachedUrlStream.java create mode 100644 songdbj/de/jarnbjo/ogg/EndOfOggStreamException.java create mode 100644 songdbj/de/jarnbjo/ogg/FileStream.java create mode 100644 songdbj/de/jarnbjo/ogg/LogicalOggStream.java create mode 100644 songdbj/de/jarnbjo/ogg/LogicalOggStreamImpl.java create mode 100644 songdbj/de/jarnbjo/ogg/OggFormatException.java create mode 100644 songdbj/de/jarnbjo/ogg/OggPage.java create mode 100644 songdbj/de/jarnbjo/ogg/OnDemandUrlStream.java create mode 100644 songdbj/de/jarnbjo/ogg/PhysicalOggStream.java create mode 100644 songdbj/de/jarnbjo/ogg/UncachedUrlStream.java create mode 100644 songdbj/de/jarnbjo/util/audio/FadeableAudioInputStream.java create mode 100644 songdbj/de/jarnbjo/util/io/BitInputStream.java create mode 100644 songdbj/de/jarnbjo/util/io/ByteArrayBitInputStream.java create mode 100644 songdbj/de/jarnbjo/util/io/HuffmanNode.java create mode 100644 songdbj/de/jarnbjo/vorbis/AudioPacket.java create mode 100644 songdbj/de/jarnbjo/vorbis/CodeBook.java create mode 100644 songdbj/de/jarnbjo/vorbis/CommentHeader.java create mode 100644 songdbj/de/jarnbjo/vorbis/Floor.java create mode 100644 songdbj/de/jarnbjo/vorbis/Floor0.java create mode 100644 songdbj/de/jarnbjo/vorbis/Floor1.java create mode 100644 songdbj/de/jarnbjo/vorbis/IdentificationHeader.java create mode 100644 songdbj/de/jarnbjo/vorbis/Mapping.java create mode 100644 songdbj/de/jarnbjo/vorbis/Mapping0.java create mode 100644 songdbj/de/jarnbjo/vorbis/MdctFloat.java create mode 100644 songdbj/de/jarnbjo/vorbis/Mode.java create mode 100644 songdbj/de/jarnbjo/vorbis/Residue.java create mode 100644 songdbj/de/jarnbjo/vorbis/Residue0.java create mode 100644 songdbj/de/jarnbjo/vorbis/Residue1.java create mode 100644 songdbj/de/jarnbjo/vorbis/Residue2.java create mode 100644 songdbj/de/jarnbjo/vorbis/SetupHeader.java create mode 100644 songdbj/de/jarnbjo/vorbis/Util.java create mode 100644 songdbj/de/jarnbjo/vorbis/VorbisAudioFileReader.java create mode 100644 songdbj/de/jarnbjo/vorbis/VorbisFormatException.java create mode 100644 songdbj/de/jarnbjo/vorbis/VorbisStream.java (limited to 'songdbj/de') diff --git a/songdbj/de/jarnbjo/ogg/BasicStream.java b/songdbj/de/jarnbjo/ogg/BasicStream.java new file mode 100644 index 0000000000..9939524d6c --- /dev/null +++ b/songdbj/de/jarnbjo/ogg/BasicStream.java @@ -0,0 +1,121 @@ +/* + * $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.3 2004/09/21 12:09:45 shred + * *** empty log message *** + * + * Revision 1.2 2004/09/21 06:38:45 shred + * Importe reorganisiert, damit Eclipse Ruhe gibt. ;-) + * + * Revision 1.1.1.1 2004/04/04 22:09:12 shred + * First Import + * + * + */ + +package de.jarnbjo.ogg; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; + +/** + * Implementation of the PhysicalOggStream interface for reading + * an Ogg stream from a URL. This class performs + * no internal caching, and will not read data from the network before + * requested to do so. It is intended to be used in non-realtime applications + * like file download managers or similar. + */ + +public class BasicStream implements PhysicalOggStream { + + private boolean closed=false; + private InputStream sourceStream; + private Object drainLock=new Object(); + private LinkedList pageCache=new LinkedList(); + private long numberOfSamples=-1; + private int position=0; + + private HashMap logicalStreams=new HashMap(); + private OggPage firstPage; + + public BasicStream(InputStream sourceStream) throws OggFormatException, IOException { + firstPage=OggPage.create(sourceStream); + position+=firstPage.getTotalLength(); + LogicalOggStreamImpl los=new LogicalOggStreamImpl(this, firstPage.getStreamSerialNumber()); + logicalStreams.put(new Integer(firstPage.getStreamSerialNumber()), los); + los.checkFormat(firstPage); + } + + public Collection getLogicalStreams() { + return logicalStreams.values(); + } + + public boolean isOpen() { + return !closed; + } + + public void close() throws IOException { + closed=true; + sourceStream.close(); + } + + public int getContentLength() { + return -1; + } + + public int getPosition() { + return position; + } + + int pageNumber=2; + + public OggPage getOggPage(int index) throws IOException { + if(firstPage!=null) { + OggPage tmp=firstPage; + firstPage=null; + return tmp; + } + else { + OggPage page=OggPage.create(sourceStream); + position+=page.getTotalLength(); + return page; + } + } + + private LogicalOggStream getLogicalStream(int serialNumber) { + return (LogicalOggStream)logicalStreams.get(new Integer(serialNumber)); + } + + public void setTime(long granulePosition) throws IOException { + throw new UnsupportedOperationException("Method not supported by this class"); + } + + /** + * @return always false + */ + + public boolean isSeekable() { + return false; + } + +} \ No newline at end of file diff --git a/songdbj/de/jarnbjo/ogg/CachedUrlStream.java b/songdbj/de/jarnbjo/ogg/CachedUrlStream.java new file mode 100644 index 0000000000..86f792e272 --- /dev/null +++ b/songdbj/de/jarnbjo/ogg/CachedUrlStream.java @@ -0,0 +1,252 @@ +/* + * $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.1 2003/04/10 19:48:22 jarnbjo + * no message + * + * + */ + +package de.jarnbjo.ogg; + +import java.io.*; +import java.net.*; +import java.util.*; + +/** + * Implementation of the PhysicalOggStream interface for reading + * and caching an Ogg stream from a URL. This class reads the data as fast as + * possible from the URL, caches it locally either in memory or on disk, and + * supports seeking within the available data. + */ + +public class CachedUrlStream implements PhysicalOggStream { + + private boolean closed=false; + private URLConnection source; + private InputStream sourceStream; + private Object drainLock=new Object(); + private RandomAccessFile drain; + private byte[] memoryCache; + private ArrayList pageOffsets=new ArrayList(); + private ArrayList pageLengths=new ArrayList(); + private long numberOfSamples=-1; + private long cacheLength; + + private HashMap logicalStreams=new HashMap(); + + private LoaderThread loaderThread; + + /** + * Creates an instance of this class, using a memory cache. + */ + + public CachedUrlStream(URL source) throws OggFormatException, IOException { + this(source, null); + } + + /** + * Creates an instance of this class, using the specified file as cache. The + * file is not automatically deleted when this class is disposed. + */ + + public CachedUrlStream(URL source, RandomAccessFile drain) throws OggFormatException, IOException { + + this.source=source.openConnection(); + + if(drain==null) { + int contentLength=this.source.getContentLength(); + if(contentLength==-1) { + throw new IOException("The URLConncetion's content length must be set when operating with a in-memory cache."); + } + memoryCache=new byte[contentLength]; + } + + this.drain=drain; + this.sourceStream=this.source.getInputStream(); + + loaderThread=new LoaderThread(sourceStream, drain, memoryCache); + new Thread(loaderThread).start(); + + while(!loaderThread.isBosDone() || pageOffsets.size()<20) { + System.out.print("pageOffsets.size(): "+pageOffsets.size()+"\r"); + try { + Thread.sleep(200); + } + catch (InterruptedException ex) { + } + } + System.out.println(); + System.out.println("caching "+pageOffsets.size()+"/20 pages\r"); + } + + public Collection getLogicalStreams() { + return logicalStreams.values(); + } + + public boolean isOpen() { + return !closed; + } + + public void close() throws IOException { + closed=true; + sourceStream.close(); + } + + public long getCacheLength() { + return cacheLength; + } + + /* + private OggPage getNextPage() throws EndOfOggStreamException, IOException, OggFormatException { + return getNextPage(false); + } + + private OggPage getNextPage(boolean skipData) throws EndOfOggStreamException, IOException, OggFormatException { + return OggPage.create(sourceStream, skipData); + } + */ + + public OggPage getOggPage(int index) throws IOException { + synchronized(drainLock) { + Long offset=(Long)pageOffsets.get(index); + Long length=(Long)pageLengths.get(index); + if(offset!=null) { + if(drain!=null) { + drain.seek(offset.longValue()); + return OggPage.create(drain); + } + else { + byte[] tmpArray=new byte[length.intValue()]; + System.arraycopy(memoryCache, offset.intValue(), tmpArray, 0, length.intValue()); + return OggPage.create(tmpArray); + } + } + else { + return null; + } + } + } + + private LogicalOggStream getLogicalStream(int serialNumber) { + return (LogicalOggStream)logicalStreams.get(new Integer(serialNumber)); + } + + public void setTime(long granulePosition) throws IOException { + for(Iterator iter=logicalStreams.values().iterator(); iter.hasNext(); ) { + LogicalOggStream los=(LogicalOggStream)iter.next(); + los.setTime(granulePosition); + } + } + + public class LoaderThread implements Runnable { + + private InputStream source; + private RandomAccessFile drain; + private byte[] memoryCache; + + private boolean bosDone=false; + + private int pageNumber; + + public LoaderThread(InputStream source, RandomAccessFile drain, byte[] memoryCache) { + this.source=source; + this.drain=drain; + this.memoryCache=memoryCache; + } + + public void run() { + try { + boolean eos=false; + byte[] buffer=new byte[8192]; + while(!eos) { + OggPage op=OggPage.create(source); + synchronized (drainLock) { + int listSize=pageOffsets.size(); + + long pos= + listSize>0? + ((Long)pageOffsets.get(listSize-1)).longValue()+ + ((Long)pageLengths.get(listSize-1)).longValue(): + 0; + + byte[] arr1=op.getHeader(); + byte[] arr2=op.getSegmentTable(); + byte[] arr3=op.getData(); + + if(drain!=null) { + drain.seek(pos); + drain.write(arr1); + drain.write(arr2); + drain.write(arr3); + } + else { + System.arraycopy(arr1, 0, memoryCache, (int)pos, arr1.length); + System.arraycopy(arr2, 0, memoryCache, (int)pos+arr1.length, arr2.length); + System.arraycopy(arr3, 0, memoryCache, (int)pos+arr1.length+arr2.length, arr3.length); + } + + pageOffsets.add(new Long(pos)); + pageLengths.add(new Long(arr1.length+arr2.length+arr3.length)); + } + + if(!op.isBos()) { + bosDone=true; + //System.out.println("bosDone=true;"); + } + if(op.isEos()) { + eos=true; + } + + LogicalOggStreamImpl los=(LogicalOggStreamImpl)getLogicalStream(op.getStreamSerialNumber()); + if(los==null) { + los=new LogicalOggStreamImpl(CachedUrlStream.this, op.getStreamSerialNumber()); + logicalStreams.put(new Integer(op.getStreamSerialNumber()), los); + los.checkFormat(op); + } + + los.addPageNumberMapping(pageNumber); + los.addGranulePosition(op.getAbsoluteGranulePosition()); + + pageNumber++; + cacheLength=op.getAbsoluteGranulePosition(); + //System.out.println("read page: "+pageNumber); + } + } + catch(EndOfOggStreamException e) { + // ok + } + catch(IOException e) { + e.printStackTrace(); + } + } + + public boolean isBosDone() { + return bosDone; + } + } + + public boolean isSeekable() { + return true; + } +} \ No newline at end of file diff --git a/songdbj/de/jarnbjo/ogg/EndOfOggStreamException.java b/songdbj/de/jarnbjo/ogg/EndOfOggStreamException.java new file mode 100644 index 0000000000..4a0c3200f4 --- /dev/null +++ b/songdbj/de/jarnbjo/ogg/EndOfOggStreamException.java @@ -0,0 +1,45 @@ +/* + * $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.2 2005/02/09 23:10:47 shred + * Serial UID für jarnbjo + * + * Revision 1.1.1.1 2004/04/04 22:09:12 shred + * First Import + * + * Revision 1.1 2003/03/03 21:02:20 jarnbjo + * no message + * + */ + + package de.jarnbjo.ogg; + +import java.io.IOException; + +/** + * Exception thrown when reaching the end of an Ogg stream + */ + +public class EndOfOggStreamException extends IOException { + private static final long serialVersionUID = 3907210438109444408L; + + public EndOfOggStreamException() { + } +} \ No newline at end of file diff --git a/songdbj/de/jarnbjo/ogg/FileStream.java b/songdbj/de/jarnbjo/ogg/FileStream.java new file mode 100644 index 0000000000..5a526300bf --- /dev/null +++ b/songdbj/de/jarnbjo/ogg/FileStream.java @@ -0,0 +1,154 @@ +/* + * $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.1 2003/04/10 19:48:22 jarnbjo + * no message + * + * + */ + +package de.jarnbjo.ogg; + +import java.io.*; +import java.util.*; + +/** + * Implementation of the PhysicalOggStream interface for accessing + * normal disk files. + */ + +public class FileStream implements PhysicalOggStream { + + private boolean closed=false; + private RandomAccessFile source; + private long[] pageOffsets; + private long numberOfSamples=-1; + + private HashMap logicalStreams=new HashMap(); + + /** + * Creates access to the specified file through the PhysicalOggStream interface. + * The specified source file must have been opened for reading. + * + * @param source the file to read from + * + * @throws OggFormatException if the stream format is incorrect + * @throws IOException if some other IO error occurs when reading the file + */ + + public FileStream(RandomAccessFile source) throws OggFormatException, IOException { + this.source=source; + + ArrayList po=new ArrayList(); + int pageNumber=0; + try { + while(true) { + po.add(new Long(this.source.getFilePointer())); + + // skip data if pageNumber>0 + OggPage op=getNextPage(pageNumber>0); + if(op==null) { + break; + } + + LogicalOggStreamImpl los=(LogicalOggStreamImpl)getLogicalStream(op.getStreamSerialNumber()); + if(los==null) { + los=new LogicalOggStreamImpl(this, op.getStreamSerialNumber()); + logicalStreams.put(new Integer(op.getStreamSerialNumber()), los); + } + + if(pageNumber==0) { + los.checkFormat(op); + } + + los.addPageNumberMapping(pageNumber); + los.addGranulePosition(op.getAbsoluteGranulePosition()); + + if(pageNumber>0) { + this.source.seek(this.source.getFilePointer()+op.getTotalLength()); + } + + pageNumber++; + } + } + catch(EndOfOggStreamException e) { + // ok + } + catch(IOException e) { + throw e; + } + //System.out.println("pageNumber: "+pageNumber); + this.source.seek(0L); + pageOffsets=new long[po.size()]; + int i=0; + Iterator iter=po.iterator(); + while(iter.hasNext()) { + pageOffsets[i++]=((Long)iter.next()).longValue(); + } + } + + public Collection getLogicalStreams() { + return logicalStreams.values(); + } + + public boolean isOpen() { + return !closed; + } + + public void close() throws IOException { + closed=true; + source.close(); + } + + private OggPage getNextPage() throws EndOfOggStreamException, IOException, OggFormatException { + return getNextPage(false); + } + + private OggPage getNextPage(boolean skipData) throws EndOfOggStreamException, IOException, OggFormatException { + return OggPage.create(source, skipData); + } + + public OggPage getOggPage(int index) throws IOException { + source.seek(pageOffsets[index]); + return OggPage.create(source); + } + + private LogicalOggStream getLogicalStream(int serialNumber) { + return (LogicalOggStream)logicalStreams.get(new Integer(serialNumber)); + } + + public void setTime(long granulePosition) throws IOException { + for(Iterator iter=logicalStreams.values().iterator(); iter.hasNext(); ) { + LogicalOggStream los=(LogicalOggStream)iter.next(); + los.setTime(granulePosition); + } + } + + /** + * @return always true + */ + + public boolean isSeekable() { + return true; + } +} \ No newline at end of file diff --git a/songdbj/de/jarnbjo/ogg/LogicalOggStream.java b/songdbj/de/jarnbjo/ogg/LogicalOggStream.java new file mode 100644 index 0000000000..2f97b2a728 --- /dev/null +++ b/songdbj/de/jarnbjo/ogg/LogicalOggStream.java @@ -0,0 +1,151 @@ +/* + * $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.2 2003/04/10 19:48:22 jarnbjo + * no message + * + * Revision 1.1 2003/03/03 21:02:20 jarnbjo + * no message + * + */ + +package de.jarnbjo.ogg; + +import java.io.IOException; + +/** + * Interface providing access to a logical Ogg stream as part of a + * physical Ogg stream. + */ + + +public interface LogicalOggStream { + + public static final String FORMAT_UNKNOWN = "application/octet-stream"; + + public static final String FORMAT_VORBIS = "audio/x-vorbis"; + public static final String FORMAT_FLAC = "audio/x-flac"; + public static final String FORMAT_THEORA = "video/x-theora"; + + /** + * Note: To read from the stream, you must use either + * this method or the method getNextOggPacket. + * Mixing calls to the two methods will cause data corruption. + * + * @return the next Ogg page + * + * @see #getNextOggPacket() + * + * @throws OggFormatException if the ogg stream is corrupted + * @throws IOException if some other IO error occurs + */ + + public OggPage getNextOggPage() throws OggFormatException, IOException; + + /** + * Note: To read from the stream, you must use either + * this method or the method getNextOggPage. + * Mixing calls to the two methods will cause data corruption. + * + * @return the next packet as a byte array + * + * @see #getNextOggPage() + * + * @throws OggFormatException if the ogg stream is corrupted + * @throws IOException if some other IO error occurs + */ + + public byte[] getNextOggPacket() throws OggFormatException, IOException; + + /** + * Checks if this stream is open for reading. + * + * @return true if this stream is open for reading, + * false otherwise + */ + + public boolean isOpen(); + + /** + * Closes this stream. After invoking this method, no further access + * to the streams data is possible. + * + * @throws IOException if an IO error occurs + */ + + public void close() throws IOException; + + /** + * Sets the stream's position to the beginning of the stream. + * This method does not work if the physical Ogg stream is not + * seekable. + * + * @throws OggFormatException if the ogg stream is corrupted + * @throws IOException if some other IO error occurs + */ + + public void reset() throws OggFormatException, IOException; + + /** + * This method does not work if the physical Ogg stream is not + * seekable. + * + * @return the granule position of the last page within + * this stream + */ + + public long getMaximumGranulePosition(); + + /** + * This method is invoked on all logical streams when + * calling the same method on the physical stream. The + * same restrictions as mentioned there apply. + * This method does not work if the physical Ogg stream is not + * seekable. + * + * @param granulePosition + * + * @see PhysicalOggStream#setTime(long) + * + * @throws IOException if an IO error occurs + */ + + public void setTime(long granulePosition) throws IOException; + + /** + * @return the last parsed granule position of this stream + */ + + public long getTime(); + + /** + * @return the content type of this stream + * + * @see #FORMAT_UNKNOWN + * @see #FORMAT_VORBIS + * @see #FORMAT_FLAC + * @see #FORMAT_THEORA + */ + + public String getFormat(); +} \ No newline at end of file diff --git a/songdbj/de/jarnbjo/ogg/LogicalOggStreamImpl.java b/songdbj/de/jarnbjo/ogg/LogicalOggStreamImpl.java new file mode 100644 index 0000000000..1a503e91ca --- /dev/null +++ b/songdbj/de/jarnbjo/ogg/LogicalOggStreamImpl.java @@ -0,0 +1,213 @@ +/* + * $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/03/31 00:23:04 jarnbjo + * no message + * + * Revision 1.2 2003/03/16 01:11:26 jarnbjo + * no message + * + * Revision 1.1 2003/03/03 21:02:20 jarnbjo + * no message + * + */ + +package de.jarnbjo.ogg; + +import java.io.*; +import java.util.*; + +public class LogicalOggStreamImpl implements LogicalOggStream { + + private PhysicalOggStream source; + private int serialNumber; + + private ArrayList pageNumberMapping=new ArrayList(); + private ArrayList granulePositions=new ArrayList(); + + private int pageIndex=0; + private OggPage currentPage; + private int currentSegmentIndex; + + private boolean open=true; + + private String format=FORMAT_UNKNOWN; + + public LogicalOggStreamImpl(PhysicalOggStream source, int serialNumber) { + this.source=source; + this.serialNumber=serialNumber; + } + + public void addPageNumberMapping(int physicalPageNumber) { + pageNumberMapping.add(new Integer(physicalPageNumber)); + } + + public void addGranulePosition(long granulePosition) { + granulePositions.add(new Long(granulePosition)); + } + + public synchronized void reset() throws OggFormatException, IOException { + currentPage=null; + currentSegmentIndex=0; + pageIndex=0; + } + + public synchronized OggPage getNextOggPage() throws EndOfOggStreamException, OggFormatException, IOException { + if(source.isSeekable()) { + currentPage=source.getOggPage(((Integer)pageNumberMapping.get(pageIndex++)).intValue()); + } + else { + currentPage=source.getOggPage(-1); + } + return currentPage; + } + + public synchronized byte[] getNextOggPacket() throws EndOfOggStreamException, OggFormatException, IOException { + ByteArrayOutputStream res=new ByteArrayOutputStream(); + int segmentLength=0; + + if(currentPage==null) { + currentPage=getNextOggPage(); + } + + do { + if(currentSegmentIndex>=currentPage.getSegmentOffsets().length) { + currentSegmentIndex=0; + + if(!currentPage.isEos()) { + if(source.isSeekable() && pageNumberMapping.size()<=pageIndex) { + while(pageNumberMapping.size()<=pageIndex+10) { + try { + Thread.sleep(1000); + } + catch (InterruptedException ex) { + } + } + } + currentPage=getNextOggPage(); + + if(res.size()==0 && currentPage.isContinued()) { + boolean done=false; + while(!done) { + if(currentPage.getSegmentLengths()[currentSegmentIndex++]!=255) { + done=true; + } + if(currentSegmentIndex>currentPage.getSegmentTable().length) { + currentPage=source.getOggPage(((Integer)pageNumberMapping.get(pageIndex++)).intValue()); + } + } + } + } + else { + throw new EndOfOggStreamException(); + } + } + segmentLength=currentPage.getSegmentLengths()[currentSegmentIndex]; + res.write(currentPage.getData(), currentPage.getSegmentOffsets()[currentSegmentIndex], segmentLength); + currentSegmentIndex++; + } while(segmentLength==255); + + return res.toByteArray(); + } + + public boolean isOpen() { + return open; + } + + public void close() throws IOException { + open=false; + } + + public long getMaximumGranulePosition() { + Long mgp=(Long)granulePositions.get(granulePositions.size()-1); + return mgp.longValue(); + } + + public synchronized long getTime() { + return currentPage!=null?currentPage.getAbsoluteGranulePosition():-1; + } + + public synchronized void setTime(long granulePosition) throws IOException { + + int page=0; + for(page=0; pagegranulePosition) { + break; + } + } + + pageIndex=page; + currentPage=source.getOggPage(((Integer)pageNumberMapping.get(pageIndex++)).intValue()); + currentSegmentIndex=0; + int segmentLength=0; + do { + if(currentSegmentIndex>=currentPage.getSegmentOffsets().length) { + currentSegmentIndex=0; + if(pageIndex>=pageNumberMapping.size()) { + throw new EndOfOggStreamException(); + } + currentPage=source.getOggPage(((Integer)pageNumberMapping.get(pageIndex++)).intValue()); + } + segmentLength=currentPage.getSegmentLengths()[currentSegmentIndex]; + currentSegmentIndex++; + } while(segmentLength==255); + } + + public void checkFormat(OggPage page) { + byte[] data=page.getData(); + + if(data.length>=7 && + data[1]==0x76 && + data[2]==0x6f && + data[3]==0x72 && + data[4]==0x62 && + data[5]==0x69 && + data[6]==0x73) { + + format=FORMAT_VORBIS; + } + else if(data.length>=7 && + data[1]==0x74 && + data[2]==0x68 && + data[3]==0x65 && + data[4]==0x6f && + data[5]==0x72 && + data[6]==0x61) { + + format=FORMAT_THEORA; + } + else if (data.length==4 && + data[0]==0x66 && + data[1]==0x4c && + data[2]==0x61 && + data[3]==0x43) { + + format=FORMAT_FLAC; + } + } + + public String getFormat() { + return format; + } +} \ No newline at end of file diff --git a/songdbj/de/jarnbjo/ogg/OggFormatException.java b/songdbj/de/jarnbjo/ogg/OggFormatException.java new file mode 100644 index 0000000000..a6b2466b92 --- /dev/null +++ b/songdbj/de/jarnbjo/ogg/OggFormatException.java @@ -0,0 +1,50 @@ +/* + * $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.2 2005/02/09 23:10:47 shred + * Serial UID für jarnbjo + * + * Revision 1.1.1.1 2004/04/04 22:09:12 shred + * First Import + * + * Revision 1.1 2003/03/03 21:02:20 jarnbjo + * no message + * + */ + +package de.jarnbjo.ogg; + +import java.io.IOException; + +/** + * Exception thrown when trying to read a corrupted Ogg stream. + */ + +public class OggFormatException extends IOException { + private static final long serialVersionUID = 3544953238333175349L; + + public OggFormatException() { + super(); + } + + public OggFormatException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/songdbj/de/jarnbjo/ogg/OggPage.java b/songdbj/de/jarnbjo/ogg/OggPage.java new file mode 100644 index 0000000000..cc965cc7a9 --- /dev/null +++ b/songdbj/de/jarnbjo/ogg/OggPage.java @@ -0,0 +1,431 @@ +/* + * $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; + } + +} \ No newline at end of file diff --git a/songdbj/de/jarnbjo/ogg/OnDemandUrlStream.java b/songdbj/de/jarnbjo/ogg/OnDemandUrlStream.java new file mode 100644 index 0000000000..98159c4e7c --- /dev/null +++ b/songdbj/de/jarnbjo/ogg/OnDemandUrlStream.java @@ -0,0 +1,127 @@ +/* + * $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.1 2003/04/10 19:48:22 jarnbjo + * no message + * + * Revision 1.1 2003/03/31 00:23:04 jarnbjo + * no message + * + */ + +package de.jarnbjo.ogg; + +import java.io.*; +import java.net.*; +import java.util.*; + +/** + * Implementation of the PhysicalOggStream interface for reading + * an Ogg stream from a URL. This class performs + * no internal caching, and will not read data from the network before + * requested to do so. It is intended to be used in non-realtime applications + * like file download managers or similar. + */ + +public class OnDemandUrlStream implements PhysicalOggStream { + + private boolean closed=false; + private URLConnection source; + private InputStream sourceStream; + private Object drainLock=new Object(); + private LinkedList pageCache=new LinkedList(); + private long numberOfSamples=-1; + private int contentLength=0; + private int position=0; + + private HashMap logicalStreams=new HashMap(); + private OggPage firstPage; + + private static final int PAGECACHE_SIZE = 20; + + public OnDemandUrlStream(URL source) throws OggFormatException, IOException { + this.source=source.openConnection(); + this.sourceStream=this.source.getInputStream(); + + contentLength=this.source.getContentLength(); + + firstPage=OggPage.create(sourceStream); + position+=firstPage.getTotalLength(); + LogicalOggStreamImpl los=new LogicalOggStreamImpl(this, firstPage.getStreamSerialNumber()); + logicalStreams.put(new Integer(firstPage.getStreamSerialNumber()), los); + los.checkFormat(firstPage); + } + + public Collection getLogicalStreams() { + return logicalStreams.values(); + } + + public boolean isOpen() { + return !closed; + } + + public void close() throws IOException { + closed=true; + sourceStream.close(); + } + + public int getContentLength() { + return contentLength; + } + + public int getPosition() { + return position; + } + + int pageNumber=2; + + public OggPage getOggPage(int index) throws IOException { + if(firstPage!=null) { + OggPage tmp=firstPage; + firstPage=null; + return tmp; + } + else { + OggPage page=OggPage.create(sourceStream); + position+=page.getTotalLength(); + return page; + } + } + + private LogicalOggStream getLogicalStream(int serialNumber) { + return (LogicalOggStream)logicalStreams.get(new Integer(serialNumber)); + } + + public void setTime(long granulePosition) throws IOException { + throw new UnsupportedOperationException("Method not supported by this class"); + } + + /** + * @return always false + */ + + public boolean isSeekable() { + return false; + } + +} \ No newline at end of file diff --git a/songdbj/de/jarnbjo/ogg/PhysicalOggStream.java b/songdbj/de/jarnbjo/ogg/PhysicalOggStream.java new file mode 100644 index 0000000000..5f342a38b7 --- /dev/null +++ b/songdbj/de/jarnbjo/ogg/PhysicalOggStream.java @@ -0,0 +1,124 @@ +/* + * $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.IOException; +import java.util.Collection; + +/** + * Interface providing access to a physical Ogg stream. Typically this is + * a file. + */ + +public interface PhysicalOggStream { + + /** + * Returns a collection of objects implementing LogicalOggStream + * for accessing the separate logical streams within this physical Ogg stream. + * + * @return a collection of objects implementing LogicalOggStream + * which are representing the logical streams contained within this + * physical stream + * + * @see LogicalOggStream + */ + + public Collection getLogicalStreams(); + + /** + * Return the Ogg page with the absolute index index, + * independent from the logical structure of this stream or if the + * index parameter is -1, the next Ogg page is returned. + * This method should only be used by implementations of LogicalOggStream + * to access the raw pages. + * + * @param index the absolute index starting from 0 at the beginning of + * the file or stream or -1 to get the next page in a non-seekable + * stream + * + * @return the Ogg page with the physical absolute index index + * + * @throws OggFormatException if the ogg stream is corrupted + * @throws IOException if some other IO error occurs + */ + + public OggPage getOggPage(int index) throws OggFormatException, IOException; + + /** + * Checks if this stream is open for reading. + * + * @return true if this stream is open for reading, + * false otherwise + */ + + public boolean isOpen(); + + /** + * Closes this stream. After invoking this method, no further access + * to the streams data is possible. + * + * @throws IOException + */ + + public void close() throws IOException; + + /** + * Sets this stream's (and its logical stream's) position to the granule + * position. The next packet read from any logical stream will be the + * first packet beginning on the first page with a granule position higher + * than the argument.

+ * + * At the moment, this method only works correctly for Ogg files with + * a single logical Vorbis stream, and due to the different interpretations + * of the granule position, depending on mixed content, this method will + * never be able to work for mixed streams. Chained and interleaved streams are + * also not yet supported. Actually, this method is only a hack to support + * seeking from JMF, but may of course be abused otherwise too :) + * + * @param granulePosition + * + * @throws OggFormatException if the ogg stream is corrupted + * @throws IOException if some other IO error occurs + */ + + public void setTime(long granulePosition) throws OggFormatException, IOException; + + /** + * @return true if the stream is seekable, false + * otherwise + */ + + public boolean isSeekable(); +} \ No newline at end of file diff --git a/songdbj/de/jarnbjo/ogg/UncachedUrlStream.java b/songdbj/de/jarnbjo/ogg/UncachedUrlStream.java new file mode 100644 index 0000000000..a07f0ac00e --- /dev/null +++ b/songdbj/de/jarnbjo/ogg/UncachedUrlStream.java @@ -0,0 +1,207 @@ +/* + * $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.1 2003/04/10 19:48:22 jarnbjo + * no message + * + */ + +package de.jarnbjo.ogg; + +import java.io.*; +import java.net.*; +import java.util.*; + +/** + * Implementation of the PhysicalOggStream interface for reading + * an Ogg stream from a URL. This class performs only the necessary caching + * to provide continous playback. Seeking within the stream is not supported. + */ + +public class UncachedUrlStream implements PhysicalOggStream { + + private boolean closed=false; + private URLConnection source; + private InputStream sourceStream; + private Object drainLock=new Object(); + private LinkedList pageCache=new LinkedList(); + private long numberOfSamples=-1; + + private HashMap logicalStreams=new HashMap(); + + private LoaderThread loaderThread; + + private static final int PAGECACHE_SIZE = 10; + + /** Creates an instance of the PhysicalOggStream interface + * suitable for reading an Ogg stream from a URL. + */ + + public UncachedUrlStream(URL source) throws OggFormatException, IOException { + + this.source=source.openConnection(); + this.sourceStream=this.source.getInputStream(); + + loaderThread=new LoaderThread(sourceStream, pageCache); + new Thread(loaderThread).start(); + + while(!loaderThread.isBosDone() || pageCache.size()PAGECACHE_SIZE) { + try { + Thread.sleep(200); + } + catch (InterruptedException ex) { + } + } + } + } + catch(EndOfOggStreamException e) { + // ok + } + catch(IOException e) { + e.printStackTrace(); + } + } + + public boolean isBosDone() { + return bosDone; + } + } + + /** + * @return always false + */ + + public boolean isSeekable() { + return false; + } + +} \ No newline at end of file diff --git a/songdbj/de/jarnbjo/util/audio/FadeableAudioInputStream.java b/songdbj/de/jarnbjo/util/audio/FadeableAudioInputStream.java new file mode 100644 index 0000000000..4916102d4b --- /dev/null +++ b/songdbj/de/jarnbjo/util/audio/FadeableAudioInputStream.java @@ -0,0 +1,62 @@ +package de.jarnbjo.util.audio; + +import java.io.*; +import javax.sound.sampled.*; + +public class FadeableAudioInputStream extends AudioInputStream { + + private AudioInputStream stream; + private boolean fading=false; + private double phi=0.0; + + public FadeableAudioInputStream(AudioInputStream stream) throws IOException { + super(stream, stream.getFormat(), -1L); + } + + public void fadeOut() { + fading=true; + phi=0.0; + } + + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + public int read(byte[] b, int offset, int length) throws IOException { + int read=super.read(b, offset, length); + + //System.out.println("read "+read); + + if(fading) { + int j=0, l=0, r=0; + double gain=0.0; + + for(int i=offset; i>8)&0xff); + b[j++]=(byte)(r&0xff); + b[j++]=(byte)((r>>8)&0xff); + } + } + + return read; + } + +} \ No newline at end of file diff --git a/songdbj/de/jarnbjo/util/io/BitInputStream.java b/songdbj/de/jarnbjo/util/io/BitInputStream.java new file mode 100644 index 0000000000..89cadb8380 --- /dev/null +++ b/songdbj/de/jarnbjo/util/io/BitInputStream.java @@ -0,0 +1,185 @@ +/* + * $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.5 2003/04/10 19:48:31 jarnbjo + * no message + * + * Revision 1.4 2003/03/16 20:57:06 jarnbjo + * no message + * + * Revision 1.3 2003/03/16 20:56:56 jarnbjo + * no message + * + * Revision 1.2 2003/03/16 01:11:39 jarnbjo + * no message + * + * Revision 1.1 2003/03/03 21:02:20 jarnbjo + * no message + * + */ + +package de.jarnbjo.util.io; + +import java.io.IOException; + +/** + * An interface with methods allowing bit-wise reading from + * an input stream. All methods in this interface are optional + * and an implementation not support a method or a specific state + * (e.g. endian) will throw an UnspportedOperationException if + * such a method is being called. This should be speicified in + * the implementation documentation. + */ + +public interface BitInputStream { + + /** + * constant for setting this stream's mode to little endian + * + * @see #setEndian(int) + */ + + public static final int LITTLE_ENDIAN = 0; + + /** + * constant for setting this stream's mode to big endian + * + * @see #setEndian(int) + */ + + public static final int BIG_ENDIAN = 1; + + /** + * reads one bit (as a boolean) from the input stream + * + * @return true if the next bit is 1, + * false otherwise + * + * @throws IOException if an I/O error occurs + * @throws UnsupportedOperationException if the method is not supported by the implementation + */ + + public boolean getBit() throws IOException; + + /** + * reads bits number of bits from the input + * stream + * + * @return the unsigned integer value read from the stream + * + * @throws IOException if an I/O error occurs + * @throws UnsupportedOperationException if the method is not supported by the implementation + */ + + public int getInt(int bits) throws IOException; + + /** + * reads bits number of bits from the input + * stream + * + * @return the signed integer value read from the stream + * + * @throws IOException if an I/O error occurs + * @throws UnsupportedOperationException if the method is not supported by the implementation + */ + + public int getSignedInt(int bits) throws IOException; + + /** + * reads a huffman codeword based on the root + * parameter and returns the decoded value + * + * @param root the root of the Huffman tree used to decode the codeword + * @return the decoded unsigned integer value read from the stream + * + * @throws IOException if an I/O error occurs + * @throws UnsupportedOperationException if the method is not supported by the implementation + */ + + public int getInt(HuffmanNode root) throws IOException; + + /** + * reads an integer encoded as "signed rice" as described in + * the FLAC audio format specification + * + * @param order + * @return the decoded integer value read from the stream + * + * @throws IOException if an I/O error occurs + * @throws UnsupportedOperationException if the method is not supported by the implementation + */ + + public int readSignedRice(int order) throws IOException; + + /** + * fills the array from offset with len + * integers encoded as "signed rice" as described in + * the FLAC audio format specification + * + * @param order + * @param buffer + * @param offset + * @param len + * @return the decoded integer value read from the stream + * + * @throws IOException if an I/O error occurs + * @throws UnsupportedOperationException if the method is not supported by the implementation + */ + + public void readSignedRice(int order, int[] buffer, int offset, int len) throws IOException; + + /** + * reads bits number of bits from the input + * stream + * + * @return the unsigned long value read from the stream + * + * @throws IOException if an I/O error occurs + * @throws UnsupportedOperationException if the method is not supported by the implementation + */ + + public long getLong(int bits) throws IOException; + + /** + * causes the read pointer to be moved to the beginning + * of the next byte, remaining bits in the current byte + * are discarded + * + * @throws UnsupportedOperationException if the method is not supported by the implementation + */ + + public void align(); + + /** + * changes the endian mode used when reading bit-wise from + * the stream, changing the mode mid-stream will cause the + * read cursor to move to the beginning of the next byte + * (as if calling the allign method + * + * @see #align() + * + * @throws UnsupportedOperationException if the method is not supported by the implementation + */ + + public void setEndian(int endian); +} \ No newline at end of file diff --git a/songdbj/de/jarnbjo/util/io/ByteArrayBitInputStream.java b/songdbj/de/jarnbjo/util/io/ByteArrayBitInputStream.java new file mode 100644 index 0000000000..9c84c7daca --- /dev/null +++ b/songdbj/de/jarnbjo/util/io/ByteArrayBitInputStream.java @@ -0,0 +1,352 @@ +/* + * $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:31 jarnbjo + * no message + * + * Revision 1.2 2003/03/16 01:11:39 jarnbjo + * no message + * + * Revision 1.1 2003/03/03 21:02:20 jarnbjo + * no message + * + */ + +package de.jarnbjo.util.io; + +import java.io.IOException; + +/** + * Implementation of the BitInputStream interface, + * using a byte array as data source. +*/ + +public class ByteArrayBitInputStream implements BitInputStream { + + private byte[] source; + private byte currentByte; + + private int endian; + + private int byteIndex=0; + private int bitIndex=0; + + public ByteArrayBitInputStream(byte[] source) { + this(source, LITTLE_ENDIAN); + } + + public ByteArrayBitInputStream(byte[] source, int endian) { + this.endian=endian; + this.source=source; + currentByte=source[0]; + bitIndex=(endian==LITTLE_ENDIAN)?0:7; + } + + public boolean getBit() throws IOException { + if(endian==LITTLE_ENDIAN) { + if(bitIndex>7) { + bitIndex=0; + currentByte=source[++byteIndex]; + } + return (currentByte&(1<<(bitIndex++)))!=0; + } + else { + if(bitIndex<0) { + bitIndex=7; + currentByte=source[++byteIndex]; + } + return (currentByte&(1<<(bitIndex--)))!=0; + } + } + + public int getInt(int bits) throws IOException { + if(bits>32) { + throw new IllegalArgumentException("Argument \"bits\" must be <= 32"); + } + int res=0; + if(endian==LITTLE_ENDIAN) { + for(int i=0; i>offset; + bitIndex-=bits; + } + else { + res=(((int)currentByte)&0xff&((1<<(bitIndex+1))-1))<<(bits-bitIndex-1); + bits-=bitIndex+1; + currentByte=source[++byteIndex]; + while(bits>=8) { + bits-=8; + res|=(((int)source[byteIndex])&0xff)<0) { + int ci=((int)source[byteIndex])&0xff; + res|=(ci>>(8-bits))&((1<=1<<(bits-1)) { + raw-=1<7) { + bitIndex=0; + currentByte=source[++byteIndex]; + } + root=(currentByte&(1<<(bitIndex++)))!=0?root.o1:root.o0; + } + return root.value.intValue(); + } + + public long getLong(int bits) throws IOException { + if(bits>64) { + throw new IllegalArgumentException("Argument \"bits\" must be <= 64"); + } + long res=0; + if(endian==LITTLE_ENDIAN) { + for(int i=0; i=0; i--) { + if(getBit()) { + res|=(1L<reads an integer encoded as "signed rice" as described in + * the FLAC audio format specification

+ * + *

not supported for little endian

+ * + * @param order + * @return the decoded integer value read from the stream + * + * @throws IOException if an I/O error occurs + * @throws UnsupportedOperationException if the method is not supported by the implementation + */ + + public int readSignedRice(int order) throws IOException { + + int msbs=-1, lsbs=0, res=0; + + if(endian==LITTLE_ENDIAN) { + // little endian + throw new UnsupportedOperationException("ByteArrayBitInputStream.readSignedRice() is only supported in big endian mode"); + } + else { + // big endian + + byte cb=source[byteIndex]; + do { + msbs++; + if(bitIndex<0) { + bitIndex=7; + byteIndex++; + cb=source[byteIndex]; + } + } while((cb&(1<>offset; + bitIndex-=bits; + } + else { + lsbs=(((int)source[byteIndex])&0xff&((1<<(bitIndex+1))-1))<<(bits-bitIndex-1); + bits-=bitIndex+1; + byteIndex++; + while(bits>=8) { + bits-=8; + lsbs|=(((int)source[byteIndex])&0xff)<0) { + int ci=((int)source[byteIndex])&0xff; + lsbs|=(ci>>(8-bits))&((1<>1)-1:(res>>1); + } + + /** + *

fills the array from offset with len + * integers encoded as "signed rice" as described in + * the FLAC audio format specification

+ * + *

not supported for little endian

+ * + * @param order + * @param buffer + * @param offset + * @param len + * @return the decoded integer value read from the stream + * + * @throws IOException if an I/O error occurs + * @throws UnsupportedOperationException if the method is not supported by the implementation + */ + + public void readSignedRice(int order, int[] buffer, int off, int len) throws IOException { + + if(endian==LITTLE_ENDIAN) { + // little endian + throw new UnsupportedOperationException("ByteArrayBitInputStream.readSignedRice() is only supported in big endian mode"); + } + else { + // big endian + for(int i=off; i>offset; + bitIndex-=bits; + } + else { + lsbs=(((int)source[byteIndex])&0xff&((1<<(bitIndex+1))-1))<<(bits-bitIndex-1); + bits-=bitIndex+1; + byteIndex++; + while(bits>=8) { + bits-=8; + lsbs|=(((int)source[byteIndex])&0xff)<0) { + int ci=((int)source[byteIndex])&0xff; + lsbs|=(ci>>(8-bits))&((1<>1)-1:(res>>1); + } + } + } + + public void align() { + if(endian==BIG_ENDIAN && bitIndex>=0) { + bitIndex=7; + byteIndex++; + } + else if(endian==LITTLE_ENDIAN && bitIndex<=7) { + bitIndex=0; + byteIndex++; + } + } + + public void setEndian(int endian) { + if(this.endian==BIG_ENDIAN && endian==LITTLE_ENDIAN) { + bitIndex=0; + byteIndex++; + } + else if(this.endian==LITTLE_ENDIAN && endian==BIG_ENDIAN) { + bitIndex=7; + byteIndex++; + } + this.endian=endian; + } + + /** + * @return the byte array used as a source for this instance + */ + + public byte[] getSource() { + return source; + } +} \ No newline at end of file diff --git a/songdbj/de/jarnbjo/util/io/HuffmanNode.java b/songdbj/de/jarnbjo/util/io/HuffmanNode.java new file mode 100644 index 0000000000..88600a4ddd --- /dev/null +++ b/songdbj/de/jarnbjo/util/io/HuffmanNode.java @@ -0,0 +1,144 @@ +/* + * $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.2 2003/04/10 19:48:31 jarnbjo + * no message + * + */ + +package de.jarnbjo.util.io; + +import java.io.IOException; +import de.jarnbjo.util.io.BitInputStream; + +/** + * Representation of a node in a Huffman tree, used to read + * Huffman compressed codewords from e.g. a Vorbis stream. + */ + +final public class HuffmanNode { + + private HuffmanNode parent; + private int depth=0; + protected HuffmanNode o0, o1; + protected Integer value; + private boolean full=false; + + /** + * creates a new Huffman tree root node + */ + + public HuffmanNode() { + this(null); + } + + protected HuffmanNode(HuffmanNode parent) { + this.parent=parent; + if(parent!=null) { + depth=parent.getDepth()+1; + } + } + + protected HuffmanNode(HuffmanNode parent, int value) { + this(parent); + this.value=new Integer(value); + full=true; + } + + protected int read(BitInputStream bis) throws IOException { + HuffmanNode iter=this; + while(iter.value==null) { + iter=bis.getBit()?iter.o1:iter.o0; + } + return iter.value.intValue(); + } + + protected HuffmanNode get0() { + return o0==null?set0(new HuffmanNode(this)):o0; + } + + protected HuffmanNode get1() { + return o1==null?set1(new HuffmanNode(this)):o1; + } + + protected Integer getValue() { + return value; + } + + private HuffmanNode getParent() { + return parent; + } + + protected int getDepth() { + return depth; + } + + private boolean isFull() { + return full?true:(full=o0!=null&&o0.isFull()&&o1!=null&&o1.isFull()); + } + + private HuffmanNode set0(HuffmanNode value) { + return o0=value; + } + + private HuffmanNode set1(HuffmanNode value) { + return o1=value; + } + + private void setValue(Integer value) { + full=true; + this.value=value; + } + + /** + * creates a new tree node at the first free location at the given + * depth, and assigns the value to it + * + * @param depth the tree depth of the new node (codeword length in bits) + * @param value the node's new value + */ + + public boolean setNewValue(int depth, int value) { + if(isFull()) { + return false; + } + if(depth==1) { + if(o0==null) { + set0(new HuffmanNode(this, value)); + return true; + } + else if(o1==null) { + set1(new HuffmanNode(this, value)); + return true; + } + else { + return false; + } + } + else { + return get0().setNewValue(depth-1, value)? + true: + get1().setNewValue(depth-1, value); + } + } +} diff --git a/songdbj/de/jarnbjo/vorbis/AudioPacket.java b/songdbj/de/jarnbjo/vorbis/AudioPacket.java new file mode 100644 index 0000000000..90a54073c1 --- /dev/null +++ b/songdbj/de/jarnbjo/vorbis/AudioPacket.java @@ -0,0 +1,328 @@ +/* + * $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.2 2004/09/21 06:39:06 shred + * Importe reorganisiert, damit Eclipse Ruhe gibt. ;-) + * + * Revision 1.1.1.1 2004/04/04 22:09:12 shred + * First Import + * + * Revision 1.2 2003/03/16 01:11:12 jarnbjo + * no message + * + * + */ + +package de.jarnbjo.vorbis; + +import java.io.IOException; + +import de.jarnbjo.util.io.BitInputStream; + +class AudioPacket { + + private int modeNumber; + private Mode mode; + private Mapping mapping; + private int n; // block size + private boolean blockFlag, previousWindowFlag, nextWindowFlag; + + private int windowCenter, leftWindowStart, leftWindowEnd, leftN, rightWindowStart, rightWindowEnd, rightN; + private float[] window; + private float[][] pcm; + private int[][] pcmInt; + + private Floor[] channelFloors; + private boolean[] noResidues; + + private final static float[][] windows=new float[8][]; + + protected AudioPacket(final VorbisStream vorbis, final BitInputStream source) throws VorbisFormatException, IOException { + + final SetupHeader sHeader=vorbis.getSetupHeader(); + final IdentificationHeader iHeader=vorbis.getIdentificationHeader(); + final Mode[] modes=sHeader.getModes(); + final Mapping[] mappings=sHeader.getMappings(); + final Residue[] residues=sHeader.getResidues(); + final int channels=iHeader.getChannels(); + + if(source.getInt(1)!=0) { + throw new VorbisFormatException("Packet type mismatch when trying to create an audio packet."); + } + + modeNumber=source.getInt(Util.ilog(modes.length-1)); + + try { + mode=modes[modeNumber]; + } + catch(ArrayIndexOutOfBoundsException e) { + throw new VorbisFormatException("Reference to invalid mode in audio packet."); + } + + mapping=mappings[mode.getMapping()]; + + final int[] magnitudes=mapping.getMagnitudes(); + final int[] angles=mapping.getAngles(); + + blockFlag=mode.getBlockFlag(); + + final int blockSize0=iHeader.getBlockSize0(); + final int blockSize1=iHeader.getBlockSize1(); + + n=blockFlag?blockSize1:blockSize0; + + if(blockFlag) { + previousWindowFlag=source.getBit(); + nextWindowFlag=source.getBit(); + } + + windowCenter=n/2; + + if(blockFlag && !previousWindowFlag) { + leftWindowStart=n/4-blockSize0/4; + leftWindowEnd=n/4+blockSize0/4; + leftN=blockSize0/2; + } + else { + leftWindowStart=0; + leftWindowEnd=n/2; + leftN=windowCenter; + } + + if(blockFlag && !nextWindowFlag) { + rightWindowStart=n*3/4-blockSize0/4; + rightWindowEnd=n*3/4+blockSize0/4; + rightN=blockSize0/2; + } + else { + rightWindowStart=windowCenter; + rightWindowEnd=n; + rightN=n/2; + } + + window=getComputedWindow();//new double[n]; + + channelFloors=new Floor[channels]; + noResidues=new boolean[channels]; + + pcm=new float[channels][n]; + pcmInt=new int[channels][n]; + + boolean allFloorsEmpty=true; + + for(int i=0; i=0; i--) { + double newA=0, newM=0; + final float[] magnitudeVector=pcm[magnitudes[i]]; + final float[] angleVector=pcm[angles[i]]; + for(int j=0; j0) { + //magnitudeVector[j]=m; + angleVector[j]=m>0?m-a:m+a; + } + else { + magnitudeVector[j]=m>0?m+a:m-a; + angleVector[j]=m; + } + } + } + + for(int i=0; i32767) val=32767; + if(val<-32768) val=-32768; + target[j1++]=val; + } + } + + // use System.arraycopy to copy the middle part (if any) + // of the window + if(leftWindowEnd+132767) val=32767; + if(val<-32768) val=-32768; + buffer[ix+(i*2)+1]=(byte)(val&0xff); + buffer[ix+(i*2)]=(byte)((val>>8)&0xff); + ix+=channels*2; + } + + ix=(leftWindowEnd-leftWindowStart)*channels*2; + for(int j=leftWindowEnd; j32767) val=32767; + if(val<-32768) val=-32768; + buffer[ix+(i*2)+1]=(byte)(val&0xff); + buffer[ix+(i*2)]=(byte)((val>>8)&0xff); + ix+=channels*2; + } + } + } + + protected float[] getWindow() { + return window; + } + + protected int getLeftWindowStart() { + return leftWindowStart; + } + + protected int getLeftWindowEnd() { + return leftWindowEnd; + } + + protected int getRightWindowStart() { + return rightWindowStart; + } + + protected int getRightWindowEnd() { + return rightWindowEnd; + } + + public int[][] getPcm() { + return pcmInt; + } + + public float[][] getFreqencyDomain() { + return pcm; + } +} diff --git a/songdbj/de/jarnbjo/vorbis/CodeBook.java b/songdbj/de/jarnbjo/vorbis/CodeBook.java new file mode 100644 index 0000000000..c865b120ca --- /dev/null +++ b/songdbj/de/jarnbjo/vorbis/CodeBook.java @@ -0,0 +1,275 @@ +/* + * $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.2 2004/09/21 06:39:06 shred + * Importe reorganisiert, damit Eclipse Ruhe gibt. ;-) + * + * Revision 1.1.1.1 2004/04/04 22:09:12 shred + * First Import + * + * Revision 1.3 2003/04/10 19:49:04 jarnbjo + * no message + * + * Revision 1.2 2003/03/16 01:11:12 jarnbjo + * no message + * + * + */ + +package de.jarnbjo.vorbis; + +import java.io.IOException; +import java.util.Arrays; + +import de.jarnbjo.util.io.BitInputStream; +import de.jarnbjo.util.io.HuffmanNode; + +class CodeBook { + + private HuffmanNode huffmanRoot; + private int dimensions, entries; + + private int[] entryLengths; + private float[][] valueVector; + + protected CodeBook(BitInputStream source) throws VorbisFormatException, IOException { + + // check sync + if(source.getInt(24)!=0x564342) { + throw new VorbisFormatException("The code book sync pattern is not correct."); + } + + dimensions=source.getInt(16); + entries=source.getInt(24); + + entryLengths=new int[entries]; + + boolean ordered=source.getBit(); + + if(ordered) { + int cl=source.getInt(5)+1; + for(int i=0; ientryLengths.length) { + throw new VorbisFormatException("The codebook entry length list is longer than the actual number of entry lengths."); + } + Arrays.fill(entryLengths, i, i+num, cl); + cl++; + i+=num; + } + } + else { + // !ordered + boolean sparse=source.getBit(); + + if(sparse) { + for(int i=0; i0) { + if(!huffmanRoot.setNewValue(el, i)) { + return false; + } + } + } + return true; + } + + protected int getDimensions() { + return dimensions; + } + + protected int getEntries() { + return entries; + } + + protected HuffmanNode getHuffmanRoot() { + return huffmanRoot; + } + + //public float[] readVQ(ReadableBitChannel source) throws IOException { + // return valueVector[readInt(source)]; + //} + + protected int readInt(final BitInputStream source) throws IOException { + return source.getInt(huffmanRoot); + /* + HuffmanNode node; + for(node=huffmanRoot; node.value==null; node=source.getBit()?node.o1:node.o0); + return node.value.intValue(); + */ + } + + protected void readVvAdd(float[][] a, BitInputStream source, int offset, int length) + throws VorbisFormatException, IOException { + + int i,j;//k;//entry; + int chptr=0; + int ch=a.length; + + if(ch==0) { + return; + } + + int lim=(offset+length)/ch; + + for(i=offset/ch;i8){ + for(i=0;iheader.getCodeBooks().length) { + throw new VorbisFormatException("A floor0_book_list entry is higher than the code book count."); + } + } + } + + protected int getType() { + return 0; + } + + protected Floor decodeFloor(VorbisStream vorbis, BitInputStream source) throws VorbisFormatException, IOException { + /** @todo implement */ + throw new UnsupportedOperationException(); + } + + protected void computeFloor(float[] vector) { + /** @todo implement */ + throw new UnsupportedOperationException(); + } + +} \ No newline at end of file diff --git a/songdbj/de/jarnbjo/vorbis/Floor1.java b/songdbj/de/jarnbjo/vorbis/Floor1.java new file mode 100644 index 0000000000..69a118b44e --- /dev/null +++ b/songdbj/de/jarnbjo/vorbis/Floor1.java @@ -0,0 +1,324 @@ +/* + * $ProjectName$ + * $ProjectRevision$ + * ----------------------------------------------------------- + * $Id$multip + * ----------------------------------------------------------- + * + * $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.2 2003/03/16 01:11:12 jarnbjo + * no message + * + * + */ + +package de.jarnbjo.vorbis; + +import java.io.IOException; +import java.util.*; + +import de.jarnbjo.util.io.BitInputStream; + + +class Floor1 extends Floor implements Cloneable { + + private int[] partitionClassList; + private int maximumClass, multiplier, rangeBits; + private int[] classDimensions; + private int[] classSubclasses; + private int[] classMasterbooks; + private int[][] subclassBooks; + private int[] xList; + private int[] yList; + private int[] lowNeighbours, highNeighbours; + //private boolean[] step2Flags; + + private static final int[] RANGES = {256, 128, 86, 64}; + + private Floor1() { + } + + protected Floor1(BitInputStream source, SetupHeader header) throws VorbisFormatException, IOException { + + maximumClass=-1; + int partitions=source.getInt(5); + partitionClassList=new int[partitions]; + + for(int i=0; imaximumClass) { + maximumClass=partitionClassList[i]; + } + } + + + classDimensions=new int[maximumClass+1]; + classSubclasses=new int[maximumClass+1]; + classMasterbooks=new int[maximumClass+1]; + subclassBooks=new int[maximumClass+1][]; + + int xListLength=2; + + for(int i=0; i<=maximumClass; i++) { + classDimensions[i]=source.getInt(3)+1; + xListLength+=classDimensions[i]; + classSubclasses[i]=source.getInt(2); + + if(classDimensions[i] > header.getCodeBooks().length || + classSubclasses[i] > header.getCodeBooks().length) { + throw new VorbisFormatException("There is a class dimension or class subclasses entry higher than the number of codebooks in the setup header."); + } + if(classSubclasses[i]!=0) { + classMasterbooks[i]=source.getInt(8); + } + subclassBooks[i]=new int[1<0) { + cval=source.getInt(vorbis.getSetupHeader().getCodeBooks()[classMasterbooks[cls]].getHuffmanRoot()); + //cval=vorbis.getSetupHeader().getCodeBooks()[classMasterbooks[cls]].readInt(source); + //System.out.println("cval: "+cval); + } + //System.out.println("0: "+cls+" "+cdim+" "+cbits+" "+csub+" "+cval); + for(int j=0; j>>=cbits; + if(book>=0) { + clone.yList[j+offset]=source.getInt(vorbis.getSetupHeader().getCodeBooks()[book].getHuffmanRoot()); + //clone.yList[j+offset]=vorbis.getSetupHeader().getCodeBooks()[book].readInt(source); + //System.out.println("b: "+(j+offset)+" "+book+" "+clone.yList[j+offset]); + //System.out.println(""); + } + else { + clone.yList[j+offset]=0; + } + } + offset+=cdim; + } + + //System.out.println(""); + //for(int i=0; i=room) { + yList[i]=highRoom>lowRoom? + val-lowRoom+predicted: + -val+highRoom+predicted-1; + } + else { + yList[i]=(val&1)==1? + predicted-((val+1)>>1): + predicted+(val>>1); + } + } + else { + step2Flags[i]=false; + yList[i]=predicted; + } + } + + final int[] xList2=new int[values]; + + System.arraycopy(xList, 0, xList2, 0, values); + sort(xList2, yList, step2Flags); + + int hx=0, hy=0, lx=0, ly=yList[0]*multiplier; + + float[] vector2=new float[vector.length]; + float[] vector3=new float[vector.length]; + Arrays.fill(vector2, 1.0f); + System.arraycopy(vector, 0, vector3, 0, vector.length); + + for(int i=1; ioff && x[j-1]>x[j]; j--) { + itmp=x[j]; + x[j]=x[j-1]; + x[j-1]=itmp; + itmp=y[j]; + y[j]=y[j-1]; + y[j-1]=itmp; + btmp=b[j]; + b[j]=b[j-1]; + b[j-1]=btmp; + //swap(x, j, j-1); + //swap(y, j, j-1); + //swap(b, j, j-1); + } + } + } + + private final static void swap(int x[], int a, int b) { + int t = x[a]; + x[a] = x[b]; + x[b] = t; + } + + private final static void swap(boolean x[], int a, int b) { + boolean t = x[a]; + x[a] = x[b]; + x[b] = t; + } +} \ No newline at end of file diff --git a/songdbj/de/jarnbjo/vorbis/IdentificationHeader.java b/songdbj/de/jarnbjo/vorbis/IdentificationHeader.java new file mode 100644 index 0000000000..1e18163385 --- /dev/null +++ b/songdbj/de/jarnbjo/vorbis/IdentificationHeader.java @@ -0,0 +1,120 @@ +/* + * $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.2 2004/09/21 06:39:06 shred + * Importe reorganisiert, damit Eclipse Ruhe gibt. ;-) + * + * Revision 1.1.1.1 2004/04/04 22:09:12 shred + * First Import + * + * Revision 1.3 2003/03/31 00:20:16 jarnbjo + * no message + * + * Revision 1.2 2003/03/16 01:11:12 jarnbjo + * no message + * + * + */ + +package de.jarnbjo.vorbis; + +import java.io.IOException; + +import de.jarnbjo.util.io.BitInputStream; + +/** + */ + +public class IdentificationHeader { + + private int version, channels, sampleRate, bitrateMaximum, bitrateNominal, bitrateMinimum, blockSize0, blockSize1; + private boolean framingFlag; + private MdctFloat[] mdct=new MdctFloat[2]; + //private MdctLong[] mdctInt=new MdctLong[2]; + + private static final long HEADER = 0x736962726f76L; // 'vorbis' + + public IdentificationHeader(BitInputStream source) throws VorbisFormatException, IOException { + //equalizer=new Equalizer(); + //equalizer.pack(); + //equalizer.show(); + + long leading=source.getLong(48); + if(leading!=HEADER) { + throw new VorbisFormatException("The identification header has an illegal leading."); + } + version=source.getInt(32); + channels=source.getInt(8); + sampleRate=source.getInt(32); + bitrateMaximum=source.getInt(32); + bitrateNominal=source.getInt(32); + bitrateMinimum=source.getInt(32); + int bs=source.getInt(8); + blockSize0=1<<(bs&0xf); + blockSize1=1<<(bs>>4); + + mdct[0]=new MdctFloat(blockSize0); + mdct[1]=new MdctFloat(blockSize1); + //mdctInt[0]=new MdctLong(blockSize0); + //mdctInt[1]=new MdctLong(blockSize1); + + framingFlag=source.getInt(8)!=0; + } + + public int getSampleRate() { + return sampleRate; + } + + public int getMaximumBitrate() { + return bitrateMaximum; + } + + public int getNominalBitrate() { + return bitrateNominal; + } + + public int getMinimumBitrate() { + return bitrateMinimum; + } + + public int getChannels() { + return channels; + } + + public int getBlockSize0() { + return blockSize0; + } + + public int getBlockSize1() { + return blockSize1; + } + + protected MdctFloat getMdct0() { + return mdct[0]; + } + + protected MdctFloat getMdct1() { + return mdct[1]; + } + + public int getVersion() { + return version; + } +} \ No newline at end of file diff --git a/songdbj/de/jarnbjo/vorbis/Mapping.java b/songdbj/de/jarnbjo/vorbis/Mapping.java new file mode 100644 index 0000000000..24a2eaa19a --- /dev/null +++ b/songdbj/de/jarnbjo/vorbis/Mapping.java @@ -0,0 +1,59 @@ +/* + * $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.2 2003/03/16 01:11:12 jarnbjo + * no message + * + * + */ + +package de.jarnbjo.vorbis; + +import java.io.IOException; + +import de.jarnbjo.util.io.BitInputStream; + +abstract class Mapping { + + protected static Mapping createInstance(VorbisStream vorbis, BitInputStream source, SetupHeader header) throws VorbisFormatException, IOException { + + int type=source.getInt(16); + switch(type) { + case 0: + //System.out.println("mapping type 0"); + return new Mapping0(vorbis, source, header); + default: + throw new VorbisFormatException("Mapping type "+type+" is not supported."); + } + } + + protected abstract int getType(); + protected abstract int[] getAngles(); + protected abstract int[] getMagnitudes() ; + protected abstract int[] getMux(); + protected abstract int[] getSubmapFloors(); + protected abstract int[] getSubmapResidues(); + protected abstract int getCouplingSteps(); + protected abstract int getSubmaps(); + +} \ No newline at end of file diff --git a/songdbj/de/jarnbjo/vorbis/Mapping0.java b/songdbj/de/jarnbjo/vorbis/Mapping0.java new file mode 100644 index 0000000000..e8fde4686f --- /dev/null +++ b/songdbj/de/jarnbjo/vorbis/Mapping0.java @@ -0,0 +1,146 @@ +/* + * $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.2 2003/03/16 01:11:12 jarnbjo + * no message + * + * + */ + +package de.jarnbjo.vorbis; + +import java.io.IOException; + +import de.jarnbjo.util.io.BitInputStream; + +class Mapping0 extends Mapping { + + private int[] magnitudes, angles, mux, submapFloors, submapResidues; + + protected Mapping0(VorbisStream vorbis, BitInputStream source, SetupHeader header) throws VorbisFormatException, IOException { + + int submaps=1; + + if(source.getBit()) { + submaps=source.getInt(4)+1; + } + + //System.out.println("submaps: "+submaps); + + int channels=vorbis.getIdentificationHeader().getChannels(); + int ilogChannels=Util.ilog(channels-1); + + //System.out.println("ilogChannels: "+ilogChannels); + + if(source.getBit()) { + int couplingSteps=source.getInt(8)+1; + magnitudes=new int[couplingSteps]; + angles=new int[couplingSteps]; + + for(int i=0; i=channels || angles[i]>=channels) { + System.err.println(magnitudes[i]); + System.err.println(angles[i]); + throw new VorbisFormatException("The channel magnitude and/or angle mismatch."); + } + } + } + else { + magnitudes=new int[0]; + angles=new int[0]; + } + + if(source.getInt(2)!=0) { + throw new VorbisFormatException("A reserved mapping field has an invalid value."); + } + + mux=new int[channels]; + if(submaps>1) { + for(int i=0; isubmaps) { + throw new VorbisFormatException("A mapping mux value is higher than the number of submaps"); + } + } + } + else { + for(int i=0; ifloorCount) { + throw new VorbisFormatException("A mapping floor value is higher than the number of floors."); + } + + if(submapResidues[i]>residueCount) { + throw new VorbisFormatException("A mapping residue value is higher than the number of residues."); + } + } + } + + protected int getType() { + return 0; + } + + protected int[] getAngles() { + return angles; + } + + protected int[] getMagnitudes() { + return magnitudes; + } + + protected int[] getMux() { + return mux; + } + + protected int[] getSubmapFloors() { + return submapFloors; + } + + protected int[] getSubmapResidues() { + return submapResidues; + } + + protected int getCouplingSteps() { + return angles.length; + } + + protected int getSubmaps() { + return submapFloors.length; + } +} \ No newline at end of file diff --git a/songdbj/de/jarnbjo/vorbis/MdctFloat.java b/songdbj/de/jarnbjo/vorbis/MdctFloat.java new file mode 100644 index 0000000000..4f354b259b --- /dev/null +++ b/songdbj/de/jarnbjo/vorbis/MdctFloat.java @@ -0,0 +1,321 @@ +/* + * $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.2 2004/09/21 12:09:45 shred + * *** empty log message *** + * + * Revision 1.1.1.1 2004/04/04 22:09:12 shred + * First Import + * + * Revision 1.3 2003/04/10 19:49:04 jarnbjo + * no message + * + * Revision 1.2 2003/03/16 01:11:12 jarnbjo + * no message + * + * + */ + +package de.jarnbjo.vorbis; + +class MdctFloat { + static private final float cPI3_8=0.38268343236508977175f; + static private final float cPI2_8=0.70710678118654752441f; + static private final float cPI1_8=0.92387953251128675613f; + + private int n; + private int log2n; + + private float[] trig; + private int[] bitrev; + + private float[] equalizer; + + private float scale; + + private int itmp1, itmp2, itmp3, itmp4, itmp5, itmp6, itmp7, itmp8, itmp9; + private float dtmp1, dtmp2, dtmp3, dtmp4, dtmp5, dtmp6, dtmp7, dtmp8, dtmp9; + + protected MdctFloat(int n) { + bitrev=new int[n/4]; + trig=new float[n+n/4]; + + int n2=n>>>1; + log2n=(int)Math.rint(Math.log(n)/Math.log(2)); + this.n=n; + + int AE=0; + int AO=1; + int BE=AE+n/2; + int BO=BE+1; + int CE=BE+n/2; + int CO=CE+1; + // trig lookups... + for(int i=0;i>>j!=0;j++) + if(((msb>>>j)&i)!=0)acc|=1<>1; + int n4=n>>2; + int n8=n>>3; + + if(equalizer!=null) { + for(int i=0; i32767.0f) temp1=32767.0f; + //if(temp1<-32768.0f) temp1=-32768.0f; + //if(temp2>32767.0f) temp2=32767.0f; + //if(temp2<-32768.0f) temp2=-32768.0f; + + pcm[o1]=(int)(-temp1*window[o1]); + pcm[o2]=(int)( temp1*window[o2]); + pcm[o3]=(int)( temp2*window[o3]); + pcm[o4]=(int)( temp2*window[o4]); + + o1++; + o2--; + o3++; + o4--; + //xx+=2; + //B+=2; + } + } + } + + private float[] kernel(float[] x, float[] w, + int n, int n2, int n4, int n8){ + // step 2 + + int xA=n4; + int xB=0; + int w2=n4; + int A=n2; + + for(int i=0;i>>(i+2); + int k1=1<<(i+3); + int wbase=n2-2; + + A=0; + float[] temp; + + for(int r=0;r<(k0>>>2);r++){ + int w1=wbase; + w2=w1-(k0>>1); + float AEv= trig[A],wA; + float AOv= trig[A+1],wB; + wbase-=2; + + k0++; + for(int s=0;s<(2<header.getMappings().length) { + throw new VorbisFormatException("Mode mapping number is higher than total number of mappings."); + } + } + + protected boolean getBlockFlag() { + return blockFlag; + } + + protected int getWindowType() { + return windowType; + } + + protected int getTransformType() { + return transformType; + } + + protected int getMapping() { + return mapping; + } +} \ No newline at end of file diff --git a/songdbj/de/jarnbjo/vorbis/Residue.java b/songdbj/de/jarnbjo/vorbis/Residue.java new file mode 100644 index 0000000000..78c28fa5ed --- /dev/null +++ b/songdbj/de/jarnbjo/vorbis/Residue.java @@ -0,0 +1,260 @@ +/* + * $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/04 08:33:02 jarnbjo + * no message + * + * Revision 1.2 2003/03/16 01:11:12 jarnbjo + * no message + * + * + */ + +package de.jarnbjo.vorbis; + +import java.io.IOException; +import java.util.HashMap; + +import de.jarnbjo.util.io.*; + + +abstract class Residue { + + protected int begin, end; + protected int partitionSize; // grouping + protected int classifications; // partitions + protected int classBook; // groupbook + protected int[] cascade; // secondstages + protected int[][] books; + protected HashMap looks=new HashMap(); + + protected Residue() { + } + + protected Residue(BitInputStream source, SetupHeader header) throws VorbisFormatException, IOException { + begin=source.getInt(24); + end=source.getInt(24); + partitionSize=source.getInt(24)+1; + classifications=source.getInt(6)+1; + classBook=source.getInt(8); + + cascade=new int[classifications]; + + int acc=0; + + for(int i=0; iheader.getCodeBooks().length) { + throw new VorbisFormatException("Reference to invalid codebook entry in residue header."); + } + } + } + } + } + + + protected static Residue createInstance(BitInputStream source, SetupHeader header) throws VorbisFormatException, IOException { + + int type=source.getInt(16); + switch(type) { + case 0: + //System.out.println("residue type 0"); + return new Residue0(source, header); + case 1: + //System.out.println("residue type 1"); + return new Residue2(source, header); + case 2: + //System.out.println("residue type 2"); + return new Residue2(source, header); + default: + throw new VorbisFormatException("Residue type "+type+" is not supported."); + } + } + + protected abstract int getType(); + protected abstract void decodeResidue(VorbisStream vorbis, BitInputStream source, Mode mode, int ch, boolean[] doNotDecodeFlags, float[][] vectors) throws VorbisFormatException, IOException; + //public abstract double[][] getDecodedVectors(); + + protected int getBegin() { + return begin; + } + + protected int getEnd() { + return end; + } + + protected int getPartitionSize() { + return partitionSize; + } + + protected int getClassifications() { + return classifications; + } + + protected int getClassBook() { + return classBook; + } + + protected int[] getCascade() { + return cascade; + } + + protected int[][] getBooks() { + return books; + } + + protected final void fill(Residue clone) { + clone.begin=begin; + clone.books=books; + clone.cascade=cascade; + clone.classBook=classBook; + clone.classifications=classifications; + clone.end=end; + clone.partitionSize=partitionSize; + } + + protected Look getLook(VorbisStream source, Mode key) { + //return new Look(source, key); + Look look=(Look)looks.get(key); + if(look==null) { + look=new Look(source, key); + looks.put(key, look); + } + return look; + } + + + class Look { + int map; + int parts; + int stages; + CodeBook[] fullbooks; + CodeBook phrasebook; + int[][] partbooks; + int partvals; + int[][] decodemap; + int postbits; + int phrasebits; + int frames; + + protected Look (VorbisStream source, Mode mode) { + int dim=0, acc=0, maxstage=0; + + map=mode.getMapping(); + parts=Residue.this.getClassifications(); + fullbooks=source.getSetupHeader().getCodeBooks(); + phrasebook=fullbooks[Residue.this.getClassBook()]; + dim=phrasebook.getDimensions(); + + partbooks=new int[parts][]; + + for(int j=0;jmaxstage) { + maxstage=stages; + } + partbooks[j]=new int[stages]; + for(int k=0; k0; x>>=1, res++); + return res; + } + + public static final float float32unpack(int x) { + float mantissa=x&0x1fffff; + float e=(x&0x7fe00000)>>21; + if((x&0x80000000)!=0) { + mantissa=-mantissa; + } + return mantissa*(float)Math.pow(2.0, e-788.0); + } + + public static final int lookup1Values(int a, int b) { + int res=(int)Math.pow(Math.E, Math.log(a)/b); + return intPow(res+1, b)<=a?res+1:res; + } + + public static final int intPow(int base, int e) { + int res=1; + for(; e>0; e--, res*=base); + return res; + } + + public static final boolean isBitSet(int value, int bit) { + return (value&(1<0) { + res+=value&1; + value>>=1; + } + return res; + } + + public static final int lowNeighbour(int[] v, int x) { + int max=-1, n=0; + for(int i=0; imax && v[i]v[x]) { + min=v[i]; + n=i; + } + } + return n; + } + + public static final int renderPoint(int x0, int x1, int y0, int y1, int x) { + int dy=y1-y0; + int ady=dy<0?-dy:dy; + int off=(ady*(x-x0))/(x1-x0); + return dy<0?y0-off:y0+off; + } + + public static final void renderLine(final int x0, final int y0, final int x1, final int y1, final float[] v) { + final int dy=y1-y0; + final int adx=x1-x0; + final int base=dy/adx; + final int sy=dy<0?base-1:base+1; + int x=x0; + int y=y0; + int err=0; + final int ady=(dy<0?-dy:dy)-(base>0?base*adx:-base*adx); + + v[x]*=Floor.DB_STATIC_TABLE[y]; + for(x=x0+1; x=adx) { + err-=adx; + v[x]*=Floor.DB_STATIC_TABLE[y+=sy]; + } + else { + v[x]*=Floor.DB_STATIC_TABLE[y+=base]; + } + } + } +} \ No newline at end of file diff --git a/songdbj/de/jarnbjo/vorbis/VorbisAudioFileReader.java b/songdbj/de/jarnbjo/vorbis/VorbisAudioFileReader.java new file mode 100644 index 0000000000..b1bc999947 --- /dev/null +++ b/songdbj/de/jarnbjo/vorbis/VorbisAudioFileReader.java @@ -0,0 +1,217 @@ +package de.jarnbjo.vorbis; + +/* + * $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.2 2004/09/21 06:39:06 shred + * Importe reorganisiert, damit Eclipse Ruhe gibt. ;-) + * + * Revision 1.1.1.1 2004/04/04 22:09:12 shred + * First Import + * + * + */ + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.net.URL; +import java.util.Collection; + +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.UnsupportedAudioFileException; +import javax.sound.sampled.spi.AudioFileReader; + +import de.jarnbjo.ogg.BasicStream; +import de.jarnbjo.ogg.EndOfOggStreamException; +import de.jarnbjo.ogg.FileStream; +import de.jarnbjo.ogg.LogicalOggStream; +import de.jarnbjo.ogg.OggFormatException; +import de.jarnbjo.ogg.PhysicalOggStream; +import de.jarnbjo.ogg.UncachedUrlStream; + +public class VorbisAudioFileReader extends AudioFileReader { + + public VorbisAudioFileReader() { + } + + public AudioFileFormat getAudioFileFormat(File file) throws IOException, UnsupportedAudioFileException { + try { + return getAudioFileFormat(new FileStream(new RandomAccessFile(file, "r"))); + } + catch(OggFormatException e) { + throw new UnsupportedAudioFileException(e.getMessage()); + } + } + + public AudioFileFormat getAudioFileFormat(InputStream stream) throws IOException, UnsupportedAudioFileException { + try { + return getAudioFileFormat(new BasicStream(stream)); + } + catch(OggFormatException e) { + throw new UnsupportedAudioFileException(e.getMessage()); + } + } + + public AudioFileFormat getAudioFileFormat(URL url) throws IOException, UnsupportedAudioFileException { + try { + return getAudioFileFormat(new UncachedUrlStream(url)); + } + catch(OggFormatException e) { + throw new UnsupportedAudioFileException(e.getMessage()); + } + } + + private AudioFileFormat getAudioFileFormat(PhysicalOggStream oggStream) throws IOException, UnsupportedAudioFileException { + try { + Collection streams=oggStream.getLogicalStreams(); + if(streams.size()!=1) { + throw new UnsupportedAudioFileException("Only Ogg files with one logical Vorbis stream are supported."); + } + + LogicalOggStream los=(LogicalOggStream)streams.iterator().next(); + if(los.getFormat()!=LogicalOggStream.FORMAT_VORBIS) { + throw new UnsupportedAudioFileException("Only Ogg files with one logical Vorbis stream are supported."); + } + + VorbisStream vs=new VorbisStream(los); + + AudioFormat audioFormat=new AudioFormat( + (float)vs.getIdentificationHeader().getSampleRate(), + 16, + vs.getIdentificationHeader().getChannels(), + true, true); + + return new AudioFileFormat(VorbisFormatType.getInstance(), audioFormat, AudioSystem.NOT_SPECIFIED); + } + catch(OggFormatException e) { + throw new UnsupportedAudioFileException(e.getMessage()); + } + catch(VorbisFormatException e) { + throw new UnsupportedAudioFileException(e.getMessage()); + } + } + + + + public AudioInputStream getAudioInputStream(File file) throws IOException, UnsupportedAudioFileException { + try { + return getAudioInputStream(new FileStream(new RandomAccessFile(file, "r"))); + } + catch(OggFormatException e) { + throw new UnsupportedAudioFileException(e.getMessage()); + } + } + + public AudioInputStream getAudioInputStream(InputStream stream) throws IOException, UnsupportedAudioFileException { + try { + return getAudioInputStream(new BasicStream(stream)); + } + catch(OggFormatException e) { + throw new UnsupportedAudioFileException(e.getMessage()); + } + } + + public AudioInputStream getAudioInputStream(URL url) throws IOException, UnsupportedAudioFileException { + try { + return getAudioInputStream(new UncachedUrlStream(url)); + } + catch(OggFormatException e) { + throw new UnsupportedAudioFileException(e.getMessage()); + } + } + + private AudioInputStream getAudioInputStream(PhysicalOggStream oggStream) throws IOException, UnsupportedAudioFileException { + try { + Collection streams=oggStream.getLogicalStreams(); + if(streams.size()!=1) { + throw new UnsupportedAudioFileException("Only Ogg files with one logical Vorbis stream are supported."); + } + + LogicalOggStream los=(LogicalOggStream)streams.iterator().next(); + if(los.getFormat()!=LogicalOggStream.FORMAT_VORBIS) { + throw new UnsupportedAudioFileException("Only Ogg files with one logical Vorbis stream are supported."); + } + + VorbisStream vs=new VorbisStream(los); + + AudioFormat audioFormat=new AudioFormat( + (float)vs.getIdentificationHeader().getSampleRate(), + 16, + vs.getIdentificationHeader().getChannels(), + true, true); + + return new AudioInputStream(new VorbisInputStream(vs), audioFormat, -1); + } + catch(OggFormatException e) { + throw new UnsupportedAudioFileException(e.getMessage()); + } + catch(VorbisFormatException e) { + throw new UnsupportedAudioFileException(e.getMessage()); + } + } + + + public static class VorbisFormatType extends AudioFileFormat.Type { + + private static final VorbisFormatType instance=new VorbisFormatType(); + + private VorbisFormatType() { + super("VORBIS", "ogg"); + } + + public static AudioFileFormat.Type getInstance() { + return instance; + } + } + + public static class VorbisInputStream extends InputStream { + + private VorbisStream source; + private byte[] buffer=new byte[8192]; + + public VorbisInputStream(VorbisStream source) { + this.source=source; + } + + public int read() throws IOException { + return 0; + } + + public int read(byte[] buffer) throws IOException { + return read(buffer, 0, buffer.length); + } + + public int read(byte[] buffer, int offset, int length) throws IOException { + try { + return source.readPcm(buffer, offset, length); + } + catch(EndOfOggStreamException e) { + return -1; + } + } + } + + +} \ No newline at end of file diff --git a/songdbj/de/jarnbjo/vorbis/VorbisFormatException.java b/songdbj/de/jarnbjo/vorbis/VorbisFormatException.java new file mode 100644 index 0000000000..5214298378 --- /dev/null +++ b/songdbj/de/jarnbjo/vorbis/VorbisFormatException.java @@ -0,0 +1,51 @@ +/* + * $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.2 2005/02/09 23:10:47 shred + * Serial UID für jarnbjo + * + * Revision 1.1.1.1 2004/04/04 22:09:12 shred + * First Import + * + * Revision 1.2 2003/03/16 01:11:12 jarnbjo + * no message + * + * + */ + +package de.jarnbjo.vorbis; + +import java.io.IOException; + +/** + * Exception thrown when trying to read a corrupted Vorbis stream. + */ + +public class VorbisFormatException extends IOException { + private static final long serialVersionUID = 3616453405694834743L; + + public VorbisFormatException() { + super(); + } + + public VorbisFormatException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/songdbj/de/jarnbjo/vorbis/VorbisStream.java b/songdbj/de/jarnbjo/vorbis/VorbisStream.java new file mode 100644 index 0000000000..36659c7106 --- /dev/null +++ b/songdbj/de/jarnbjo/vorbis/VorbisStream.java @@ -0,0 +1,247 @@ +/* + * $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.4 2003/04/10 19:49:04 jarnbjo + * no message + * + * Revision 1.3 2003/03/31 00:20:16 jarnbjo + * no message + * + * Revision 1.2 2003/03/16 01:11:12 jarnbjo + * no message + * + * + */ + +package de.jarnbjo.vorbis; + +import java.io.*; +import java.util.*; + +import de.jarnbjo.ogg.*; +import de.jarnbjo.util.io.*; + +/** + */ + +public class VorbisStream { + + private LogicalOggStream oggStream; + private IdentificationHeader identificationHeader; + private CommentHeader commentHeader; + private SetupHeader setupHeader; + + private AudioPacket lastAudioPacket, nextAudioPacket; + private LinkedList audioPackets=new LinkedList(); + private byte[] currentPcm; + private int currentPcmIndex; + private int currentPcmLimit; + + private static final int IDENTIFICATION_HEADER = 1; + private static final int COMMENT_HEADER = 3; + private static final int SETUP_HEADER = 5; + + private int bitIndex=0; + private byte lastByte=(byte)0; + private boolean initialized=false; + + private Object streamLock=new Object(); + private int pageCounter=0; + + private int currentBitRate=0; + + private long currentGranulePosition; + + public static final int BIG_ENDIAN = 0; + public static final int LITTLE_ENDIAN = 1; + + public VorbisStream() { + } + + public VorbisStream(LogicalOggStream oggStream) throws VorbisFormatException, IOException { + this.oggStream=oggStream; + + for(int i=0; i<3; i++) { + BitInputStream source=new ByteArrayBitInputStream(oggStream.getNextOggPacket()); + int headerType=source.getInt(8); + switch(headerType) { + case IDENTIFICATION_HEADER: + identificationHeader=new IdentificationHeader(source); + break; + case COMMENT_HEADER: + commentHeader=new CommentHeader(source); + break; + case SETUP_HEADER: + setupHeader=new SetupHeader(this, source); + break; + } + } + + if(identificationHeader==null) { + throw new VorbisFormatException("The file has no identification header."); + } + + if(commentHeader==null) { + throw new VorbisFormatException("The file has no commentHeader."); + } + + if(setupHeader==null) { + throw new VorbisFormatException("The file has no setup header."); + } + + //currentPcm=new int[identificationHeader.getChannels()][16384]; + currentPcm=new byte[identificationHeader.getChannels()*identificationHeader.getBlockSize1()*2]; + //new BufferThread().start(); + } + + public IdentificationHeader getIdentificationHeader() { + return identificationHeader; + } + + public CommentHeader getCommentHeader() { + return commentHeader; + } + + protected SetupHeader getSetupHeader() { + return setupHeader; + } + + public boolean isOpen() { + return oggStream.isOpen(); + } + + public void close() throws IOException { + oggStream.close(); + } + + + public int readPcm(byte[] buffer, int offset, int length) throws IOException { + synchronized (streamLock) { + final int channels=identificationHeader.getChannels(); + + if(lastAudioPacket==null) { + lastAudioPacket=getNextAudioPacket(); + } + if(currentPcm==null || currentPcmIndex>=currentPcmLimit) { + AudioPacket ap=getNextAudioPacket(); + try { + ap.getPcm(lastAudioPacket, currentPcm); + currentPcmLimit=ap.getNumberOfSamples()*identificationHeader.getChannels()*2; + } + catch(ArrayIndexOutOfBoundsException e) { + return 0; + } + currentPcmIndex=0; + lastAudioPacket=ap; + } + int written=0; + int i=0; + int arrIx=0; + for(i=currentPcmIndex; i