From f1361cc9e6cd80c49658eefe4e2785f7aaf5c0a5 Mon Sep 17 00:00:00 2001 From: "Sha..rd" Date: Sun, 22 May 2011 01:21:02 +0000 Subject: [PATCH] * OGG loader now supports stream-cache feature git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@7516 75d07b2b-3a1a-0410-a2c5-0572b91ccdca --- .../jme3/audio/plugins/CachedOggStream.java | 102 +++++------------- .../com/jme3/audio/plugins/OGGLoader.java | 97 ++++++++++++----- .../jme3/audio/plugins/UncachedOggStream.java | 24 ++++- 3 files changed, 122 insertions(+), 101 deletions(-) diff --git a/engine/src/jogg/com/jme3/audio/plugins/CachedOggStream.java b/engine/src/jogg/com/jme3/audio/plugins/CachedOggStream.java index 693efb862..ccfc49a43 100644 --- a/engine/src/jogg/com/jme3/audio/plugins/CachedOggStream.java +++ b/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 pageOffsets = new ArrayList(); - private ArrayList pageLengths = new ArrayList(); - private long cacheLength; - - private boolean bos = false; private boolean eos = false; + private boolean bos = false; + private InputStream sourceStream; + private HashMap logicalStreams + = new HashMap(); + + private IntMap oggPages = new IntMap(); + private OggPage lastPage; + private int pageNumber; + + public CachedOggStream(InputStream in) throws IOException { + sourceStream = in; - private HashMap logicalStreams - = new HashMap(); - - /** - * 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 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; } diff --git a/engine/src/jogg/com/jme3/audio/plugins/OGGLoader.java b/engine/src/jogg/com/jme3/audio/plugins/OGGLoader.java index 9d279b5a4..ae3b02efb 100644 --- a/engine/src/jogg/com/jme3/audio/plugins/OGGLoader.java +++ b/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 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; } } diff --git a/engine/src/jogg/com/jme3/audio/plugins/UncachedOggStream.java b/engine/src/jogg/com/jme3/audio/plugins/UncachedOggStream.java index a588c57c9..7aa3ea29e 100644 --- a/engine/src/jogg/com/jme3/audio/plugins/UncachedOggStream.java +++ b/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 pageCache = new LinkedList(); - private HashMap logicalStreams = new HashMap(); + private HashMap logicalStreams + = new HashMap(); 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(); }