Explorar o código

* OGG loader now supports stream-cache feature

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@7516 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
Sha..rd %!s(int64=14) %!d(string=hai) anos
pai
achega
f1361cc9e6

+ 29 - 73
engine/src/jogg/com/jme3/audio/plugins/CachedOggStream.java

@@ -32,13 +32,13 @@
 
 package com.jme3.audio.plugins;
 
+import com.jme3.util.IntMap;
 import de.jarnbjo.ogg.LogicalOggStream;
 import de.jarnbjo.ogg.LogicalOggStreamImpl;
 import de.jarnbjo.ogg.OggPage;
 import de.jarnbjo.ogg.PhysicalOggStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Collection;
 import java.util.logging.Level;
@@ -52,35 +52,36 @@ import java.util.logging.Logger;
  */
 public class CachedOggStream implements PhysicalOggStream {
 
-    private static final Logger logger = Logger.getLogger(CachedOggStream.class.getName());
-
     private boolean closed = false;
-    private InputStream sourceStream;
-    private byte[] memoryCache;
-    private ArrayList<Long> pageOffsets = new ArrayList<Long>();
-    private ArrayList<Long> pageLengths = new ArrayList<Long>();
-    private long cacheLength;
-
-    private boolean bos = false;
     private boolean eos = false;
+    private boolean bos = false;
+    private InputStream sourceStream;
+    private HashMap<Integer, LogicalOggStream> logicalStreams 
+            = new HashMap<Integer, LogicalOggStream>();
+    
+    private IntMap<OggPage> oggPages = new IntMap<OggPage>();
+    private OggPage lastPage;
+   
     private int pageNumber;
+    
+    public CachedOggStream(InputStream in) throws IOException {
+        sourceStream = in;
 
-   private HashMap<Integer, LogicalOggStream> logicalStreams
-           = new HashMap<Integer, LogicalOggStream>();
-
-    /**
-     *  Creates an instance of this class, using the specified file as cache. The
-     *  file is not automatically deleted when this class is disposed.
-     */
-    public CachedOggStream(InputStream stream, int length, int numPages) throws IOException {
-        logger.log(Level.INFO, "Creating memory cache of size {0}", length);
-
-        memoryCache = new byte[length];
-        sourceStream = stream;
-
+        // Read all OGG pages in file
+        long time = System.nanoTime();
         while (!eos){
             readOggNextPage();
         }
+        long dt = System.nanoTime() - time;
+        Logger.getLogger(CachedOggStream.class.getName()).log(Level.INFO, "Took {0} ms to load OGG", dt/1000000);
+    }
+
+    public OggPage getLastOggPage() {
+        return lastPage;
+    }
+    
+    private LogicalOggStream getLogicalStream(int serialNumber) {
+        return logicalStreams.get(new Integer(serialNumber));
     }
 
     public Collection<LogicalOggStream> getLogicalStreams() {
@@ -96,76 +97,31 @@ public class CachedOggStream implements PhysicalOggStream {
         sourceStream.close();
     }
 
-   public long getCacheLength() {
-      return cacheLength;
-   }
-
     public OggPage getOggPage(int index) throws IOException {
-        Long offset = (Long) pageOffsets.get(index);
-        Long length = (Long) pageLengths.get(index);
-
-        byte[] tmpArray = new byte[length.intValue()];
-        System.arraycopy(memoryCache, offset.intValue(), tmpArray, 0, length.intValue());
-        return OggPage.create(tmpArray);
+        return oggPages.get(index);
     }
 
-   /**
-    * Set the current time as granule position
-    * @param granulePosition
-    * @throws IOException
-    */
    public void setTime(long granulePosition) throws IOException {
        for (LogicalOggStream los : getLogicalStreams()){
            los.setTime(granulePosition);
        }
    }
 
-   /**
-    * Read an OggPage from the input stream and put it in the file's cache.
-    * @return the page number
-    * @throws IOException
-    */
    private int readOggNextPage() throws IOException {
-       if (eos) // end of stream
+       if (eos)
            return -1;
 
-       // create ogg page for the stream
        OggPage op = OggPage.create(sourceStream);
-
-       // find location where to write ogg page
-       // based on the last ogg page's offset and length.
-       int listSize = pageOffsets.size();
-       long pos = listSize > 0 ? pageOffsets.get(listSize - 1) + pageLengths.get(listSize - 1) : 0;
-
-       // various data in the ogg page that is needed
-       byte[] arr1 = op.getHeader();
-       byte[] arr2 = op.getSegmentTable();
-       byte[] arr3 = op.getData();
-
-       // put in the memory cache
-       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);
-
-       // append the information of the ogg page into the offset and length lists
-       pageOffsets.add(pos);
-       pageLengths.add((long) (arr1.length + arr2.length + arr3.length));
-
-       // check for beginning of stream
-       if (op.isBos()){
+       if (!op.isBos()){
            bos = true;
        }
-
-       // check for end of stream
        if (op.isEos()){
            eos = true;
+           lastPage = op;
        }
 
-       // find the logical ogg stream, if it was created already, based on
-       // the stream serial
        LogicalOggStreamImpl los = (LogicalOggStreamImpl) logicalStreams.get(op.getStreamSerialNumber());
        if(los == null) {
-           // not created, make a new one
           los = new LogicalOggStreamImpl(this, op.getStreamSerialNumber());
           logicalStreams.put(op.getStreamSerialNumber(), los);
           los.checkFormat(op);
@@ -174,8 +130,8 @@ public class CachedOggStream implements PhysicalOggStream {
        los.addPageNumberMapping(pageNumber);
        los.addGranulePosition(op.getAbsoluteGranulePosition());
 
+       oggPages.put(pageNumber, op);
        pageNumber++;
-       cacheLength = op.getAbsoluteGranulePosition();
 
        return pageNumber-1;
    }

+ 73 - 24
engine/src/jogg/com/jme3/audio/plugins/OGGLoader.java

@@ -40,6 +40,7 @@ import com.jme3.audio.AudioKey;
 import com.jme3.util.BufferUtils;
 import de.jarnbjo.ogg.EndOfOggStreamException;
 import de.jarnbjo.ogg.LogicalOggStream;
+import de.jarnbjo.ogg.PhysicalOggStream;
 import de.jarnbjo.vorbis.IdentificationHeader;
 import de.jarnbjo.vorbis.VorbisStream;
 import java.io.ByteArrayOutputStream;
@@ -52,13 +53,13 @@ public class OGGLoader implements AssetLoader {
 
 //    private static int BLOCK_SIZE = 4096*64;
 
-    private UncachedOggStream oggStream;
+    private PhysicalOggStream oggStream;
     private LogicalOggStream loStream;
     private VorbisStream vorbisStream;
 
 //    private CommentHeader commentHdr;
     private IdentificationHeader streamHdr;
-
+  
     private static class JOggInputStream extends InputStream {
 
         private boolean endOfStream = false;
@@ -105,6 +106,54 @@ public class OGGLoader implements AssetLoader {
         }
 
     }
+    
+    /**
+     * Returns the total of expected OGG bytes. 
+     * 
+     * @param dataBytesTotal The number of bytes in the input
+     * @return If the computed number of bytes is less than the number
+     * of bytes in the input, it is returned, otherwise the number 
+     * of bytes in the input is returned.
+     */
+    private int getOggTotalBytes(int dataBytesTotal){
+        // Vorbis stream could have more samples than than the duration of the sound
+        // Must truncate.
+        int numSamples;
+        if (oggStream instanceof CachedOggStream){
+            CachedOggStream cachedOggStream = (CachedOggStream) oggStream;
+            numSamples = (int) cachedOggStream.getLastOggPage().getAbsoluteGranulePosition();
+        }else{
+            UncachedOggStream uncachedOggStream = (UncachedOggStream) oggStream;
+            numSamples = (int) uncachedOggStream.getLastOggPage().getAbsoluteGranulePosition();
+        }
+
+        // Number of Samples * Number of Channels * Bytes Per Sample
+        int totalBytes = numSamples * streamHdr.getChannels() * 2;
+
+//        System.out.println("Sample Rate: " + streamHdr.getSampleRate());
+//        System.out.println("Channels: " + streamHdr.getChannels());
+//        System.out.println("Stream Length: " + numSamples);
+//        System.out.println("Bytes Calculated: " + totalBytes);
+//        System.out.println("Bytes Available:  " + dataBytes.length);
+
+        // Take the minimum of the number of bytes available
+        // and the expected duration of the audio.
+        return Math.min(totalBytes, dataBytesTotal);
+    }
+    
+    private float computeStreamDuration(){
+        // for uncached stream sources, the granule position is not known.
+        if (oggStream instanceof UncachedOggStream)
+            return -1;
+        
+        // 2 bytes(16bit) * channels * sampleRate
+        int bytesPerSec = 2 * streamHdr.getChannels() * streamHdr.getSampleRate();
+        
+        // Don't know how many bytes are in input, pass MAX_VALUE
+        int totalBytes = getOggTotalBytes(Integer.MAX_VALUE);
+        
+        return (float)totalBytes / bytesPerSec;
+    }
 
     private ByteBuffer readToBuffer() throws IOException{
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -122,22 +171,8 @@ public class OGGLoader implements AssetLoader {
        
         byte[] dataBytes = baos.toByteArray();
         swapBytes(dataBytes, 0, dataBytes.length);
-        // Vorbis stream could have more samples than than the duration of the sound
-        // Must truncate.
-        int numSamples =  (int) oggStream.getLastOggPage().getAbsoluteGranulePosition();
-
-        // Number of Samples * Number of Channels * Bytes Per Sample
-        int totalBytes = numSamples * streamHdr.getChannels() * 2;
-
-//        System.out.println("Sample Rate: " + streamHdr.getSampleRate());
-//        System.out.println("Channels: " + streamHdr.getChannels());
-//        System.out.println("Stream Length: " + numSamples);
-//        System.out.println("Bytes Calculated: " + totalBytes);
-//        System.out.println("Bytes Available:  " + dataBytes.length);
-
-        // Take the minimum of the number of bytes available
-        // and the expected duration of the audio.
-        int bytesToCopy = Math.min(totalBytes, dataBytes.length);
+        
+        int bytesToCopy = getOggTotalBytes( dataBytes.length );
 
         ByteBuffer data = BufferUtils.createByteBuffer(bytesToCopy);
         data.put(dataBytes, 0, bytesToCopy).flip();
@@ -149,7 +184,7 @@ public class OGGLoader implements AssetLoader {
         return data;
     }
 
-    private static final void swapBytes(byte[] b, int off, int len) {
+    private static void swapBytes(byte[] b, int off, int len) {
         byte tempByte;
         for (int i = off; i < (off+len); i+=2) {
             tempByte = b[i];
@@ -163,8 +198,20 @@ public class OGGLoader implements AssetLoader {
     }
 
     public Object load(AssetInfo info) throws IOException {
+        if (!(info.getKey() instanceof AudioKey)){
+            throw new IllegalArgumentException("Audio assets must be loaded using an AudioKey");
+        }
+        
+        AudioKey key = (AudioKey) info.getKey();
+        boolean readStream = key.isStream();
+        boolean streamCache = key.useStreamCache();
+        
         InputStream in = info.openStream();
-        oggStream = new UncachedOggStream(in);
+        if (readStream && streamCache){
+            oggStream = new CachedOggStream(in);
+        }else{
+            oggStream = new UncachedOggStream(in);
+        }
 
         Collection<LogicalOggStream> streams = oggStream.getLogicalStreams();
         loStream = streams.iterator().next();
@@ -176,9 +223,7 @@ public class OGGLoader implements AssetLoader {
         vorbisStream = new VorbisStream(loStream);
         streamHdr = vorbisStream.getIdentificationHeader();
 //        commentHdr = vorbisStream.getCommentHeader();
-
-        boolean readStream = ((AudioKey)info.getKey()).isStream();
-        
+    
         if (!readStream){
             AudioBuffer audioBuffer = new AudioBuffer();
             audioBuffer.setupFormat(streamHdr.getChannels(), 16, streamHdr.getSampleRate());
@@ -187,7 +232,11 @@ public class OGGLoader implements AssetLoader {
         }else{
             AudioStream audioStream = new AudioStream();
             audioStream.setupFormat(streamHdr.getChannels(), 16, streamHdr.getSampleRate());
-            audioStream.updateData(readToStream(), -1);
+            
+            // might return -1 if unknown
+            float streamDuration = computeStreamDuration();
+            
+            audioStream.updateData(readToStream(), streamDuration);
             return audioStream;
         }
     }

+ 20 - 4
engine/src/jogg/com/jme3/audio/plugins/UncachedOggStream.java

@@ -54,7 +54,8 @@ public class UncachedOggStream implements PhysicalOggStream {
     private boolean bos = false;
     private InputStream sourceStream;
     private LinkedList<OggPage> pageCache = new LinkedList<OggPage>();
-    private HashMap<Integer, LogicalOggStream> logicalStreams = new HashMap();
+    private HashMap<Integer, LogicalOggStream> logicalStreams 
+            = new HashMap<Integer, LogicalOggStream>();
     private OggPage lastPage = null;
 
     public UncachedOggStream(InputStream in) throws OggFormatException, IOException {
@@ -64,6 +65,11 @@ public class UncachedOggStream implements PhysicalOggStream {
         while (!bos){
             readNextOggPage();
         }
+
+        // now buffer up an addition 25 pages
+//        while (pageCache.size() < 25 && !eos){
+//            readNextOggPage();
+//        }
     }
 
     public OggPage getLastOggPage() {
@@ -95,9 +101,19 @@ public class UncachedOggStream implements PhysicalOggStream {
             return null;
         }
 
-        if (pageCache.size() == 0){
-            readNextOggPage();
-        }
+//        if (!eos){
+//            int num = pageCache.size();
+//            long fiveMillis = 5000000;
+//            long timeStart  = System.nanoTime();
+//            do {
+//                readNextOggPage();
+//            } while ( !eos && (System.nanoTime() - timeStart) < fiveMillis );
+//            System.out.println( pageCache.size() - num );
+
+            if (pageCache.size() == 0 /*&& !eos*/){
+                readNextOggPage();
+            }
+//        }
 
         return pageCache.removeFirst();
     }