* OGG loader now supports stream-cache feature

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@7516 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
Sha..rd 14 years ago
parent 3a938b7dc9
commit f1361cc9e6
  1. 102
      engine/src/jogg/com/jme3/audio/plugins/CachedOggStream.java
  2. 97
      engine/src/jogg/com/jme3/audio/plugins/OGGLoader.java
  3. 24
      engine/src/jogg/com/jme3/audio/plugins/UncachedOggStream.java

@ -32,13 +32,13 @@
package com.jme3.audio.plugins; package com.jme3.audio.plugins;
import com.jme3.util.IntMap;
import de.jarnbjo.ogg.LogicalOggStream; import de.jarnbjo.ogg.LogicalOggStream;
import de.jarnbjo.ogg.LogicalOggStreamImpl; import de.jarnbjo.ogg.LogicalOggStreamImpl;
import de.jarnbjo.ogg.OggPage; import de.jarnbjo.ogg.OggPage;
import de.jarnbjo.ogg.PhysicalOggStream; import de.jarnbjo.ogg.PhysicalOggStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Collection; import java.util.Collection;
import java.util.logging.Level; import java.util.logging.Level;
@ -52,35 +52,36 @@ import java.util.logging.Logger;
*/ */
public class CachedOggStream implements PhysicalOggStream { public class CachedOggStream implements PhysicalOggStream {
private static final Logger logger = Logger.getLogger(CachedOggStream.class.getName());
private boolean closed = false; 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 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; private int pageNumber;
public CachedOggStream(InputStream in) throws IOException {
sourceStream = in;
private HashMap<Integer, LogicalOggStream> logicalStreams // Read all OGG pages in file
= new HashMap<Integer, LogicalOggStream>(); long time = System.nanoTime();
/**
* 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;
while (!eos){ while (!eos){
readOggNextPage(); 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() { public Collection<LogicalOggStream> getLogicalStreams() {
@ -96,76 +97,31 @@ public class CachedOggStream implements PhysicalOggStream {
sourceStream.close(); sourceStream.close();
} }
public long getCacheLength() {
return cacheLength;
}
public OggPage getOggPage(int index) throws IOException { public OggPage getOggPage(int index) throws IOException {
Long offset = (Long) pageOffsets.get(index); return oggPages.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);
} }
/**
* Set the current time as granule position
* @param granulePosition
* @throws IOException
*/
public void setTime(long granulePosition) throws IOException { public void setTime(long granulePosition) throws IOException {
for (LogicalOggStream los : getLogicalStreams()){ for (LogicalOggStream los : getLogicalStreams()){
los.setTime(granulePosition); 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 { private int readOggNextPage() throws IOException {
if (eos) // end of stream if (eos)
return -1; return -1;
// create ogg page for the stream
OggPage op = OggPage.create(sourceStream); OggPage op = OggPage.create(sourceStream);
if (!op.isBos()){
// 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()){
bos = true; bos = true;
} }
// check for end of stream
if (op.isEos()){ if (op.isEos()){
eos = true; 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()); LogicalOggStreamImpl los = (LogicalOggStreamImpl) logicalStreams.get(op.getStreamSerialNumber());
if(los == null) { if(los == null) {
// not created, make a new one
los = new LogicalOggStreamImpl(this, op.getStreamSerialNumber()); los = new LogicalOggStreamImpl(this, op.getStreamSerialNumber());
logicalStreams.put(op.getStreamSerialNumber(), los); logicalStreams.put(op.getStreamSerialNumber(), los);
los.checkFormat(op); los.checkFormat(op);
@ -174,8 +130,8 @@ public class CachedOggStream implements PhysicalOggStream {
los.addPageNumberMapping(pageNumber); los.addPageNumberMapping(pageNumber);
los.addGranulePosition(op.getAbsoluteGranulePosition()); los.addGranulePosition(op.getAbsoluteGranulePosition());
oggPages.put(pageNumber, op);
pageNumber++; pageNumber++;
cacheLength = op.getAbsoluteGranulePosition();
return pageNumber-1; return pageNumber-1;
} }

@ -40,6 +40,7 @@ import com.jme3.audio.AudioKey;
import com.jme3.util.BufferUtils; import com.jme3.util.BufferUtils;
import de.jarnbjo.ogg.EndOfOggStreamException; import de.jarnbjo.ogg.EndOfOggStreamException;
import de.jarnbjo.ogg.LogicalOggStream; import de.jarnbjo.ogg.LogicalOggStream;
import de.jarnbjo.ogg.PhysicalOggStream;
import de.jarnbjo.vorbis.IdentificationHeader; import de.jarnbjo.vorbis.IdentificationHeader;
import de.jarnbjo.vorbis.VorbisStream; import de.jarnbjo.vorbis.VorbisStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@ -52,13 +53,13 @@ public class OGGLoader implements AssetLoader {
// private static int BLOCK_SIZE = 4096*64; // private static int BLOCK_SIZE = 4096*64;
private UncachedOggStream oggStream; private PhysicalOggStream oggStream;
private LogicalOggStream loStream; private LogicalOggStream loStream;
private VorbisStream vorbisStream; private VorbisStream vorbisStream;
// private CommentHeader commentHdr; // private CommentHeader commentHdr;
private IdentificationHeader streamHdr; private IdentificationHeader streamHdr;
private static class JOggInputStream extends InputStream { private static class JOggInputStream extends InputStream {
private boolean endOfStream = false; 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{ private ByteBuffer readToBuffer() throws IOException{
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
@ -122,22 +171,8 @@ public class OGGLoader implements AssetLoader {
byte[] dataBytes = baos.toByteArray(); byte[] dataBytes = baos.toByteArray();
swapBytes(dataBytes, 0, dataBytes.length); swapBytes(dataBytes, 0, dataBytes.length);
// Vorbis stream could have more samples than than the duration of the sound
// Must truncate. int bytesToCopy = getOggTotalBytes( dataBytes.length );
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);
ByteBuffer data = BufferUtils.createByteBuffer(bytesToCopy); ByteBuffer data = BufferUtils.createByteBuffer(bytesToCopy);
data.put(dataBytes, 0, bytesToCopy).flip(); data.put(dataBytes, 0, bytesToCopy).flip();
@ -149,7 +184,7 @@ public class OGGLoader implements AssetLoader {
return data; 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; byte tempByte;
for (int i = off; i < (off+len); i+=2) { for (int i = off; i < (off+len); i+=2) {
tempByte = b[i]; tempByte = b[i];
@ -163,8 +198,20 @@ public class OGGLoader implements AssetLoader {
} }
public Object load(AssetInfo info) throws IOException { 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(); 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(); Collection<LogicalOggStream> streams = oggStream.getLogicalStreams();
loStream = streams.iterator().next(); loStream = streams.iterator().next();
@ -176,9 +223,7 @@ public class OGGLoader implements AssetLoader {
vorbisStream = new VorbisStream(loStream); vorbisStream = new VorbisStream(loStream);
streamHdr = vorbisStream.getIdentificationHeader(); streamHdr = vorbisStream.getIdentificationHeader();
// commentHdr = vorbisStream.getCommentHeader(); // commentHdr = vorbisStream.getCommentHeader();
boolean readStream = ((AudioKey)info.getKey()).isStream();
if (!readStream){ if (!readStream){
AudioBuffer audioBuffer = new AudioBuffer(); AudioBuffer audioBuffer = new AudioBuffer();
audioBuffer.setupFormat(streamHdr.getChannels(), 16, streamHdr.getSampleRate()); audioBuffer.setupFormat(streamHdr.getChannels(), 16, streamHdr.getSampleRate());
@ -187,7 +232,11 @@ public class OGGLoader implements AssetLoader {
}else{ }else{
AudioStream audioStream = new AudioStream(); AudioStream audioStream = new AudioStream();
audioStream.setupFormat(streamHdr.getChannels(), 16, streamHdr.getSampleRate()); 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; return audioStream;
} }
} }

@ -54,7 +54,8 @@ public class UncachedOggStream implements PhysicalOggStream {
private boolean bos = false; private boolean bos = false;
private InputStream sourceStream; private InputStream sourceStream;
private LinkedList<OggPage> pageCache = new LinkedList<OggPage>(); 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; private OggPage lastPage = null;
public UncachedOggStream(InputStream in) throws OggFormatException, IOException { public UncachedOggStream(InputStream in) throws OggFormatException, IOException {
@ -64,6 +65,11 @@ public class UncachedOggStream implements PhysicalOggStream {
while (!bos){ while (!bos){
readNextOggPage(); readNextOggPage();
} }
// now buffer up an addition 25 pages
// while (pageCache.size() < 25 && !eos){
// readNextOggPage();
// }
} }
public OggPage getLastOggPage() { public OggPage getLastOggPage() {
@ -95,9 +101,19 @@ public class UncachedOggStream implements PhysicalOggStream {
return null; return null;
} }
if (pageCache.size() == 0){ // if (!eos){
readNextOggPage(); // 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(); return pageCache.removeFirst();
} }

Loading…
Cancel
Save