* 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. 98
      engine/src/jogg/com/jme3/audio/plugins/CachedOggStream.java
  2. 91
      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;
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 int pageNumber;
private boolean bos = false;
private InputStream sourceStream;
private HashMap<Integer, LogicalOggStream> logicalStreams
= new HashMap<Integer, LogicalOggStream>();
private HashMap<Integer, LogicalOggStream> logicalStreams
= new HashMap<Integer, LogicalOggStream>();
private IntMap<OggPage> oggPages = new IntMap<OggPage>();
private OggPage lastPage;
/**
* 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);
private int pageNumber;
memoryCache = new byte[length];
sourceStream = stream;
public CachedOggStream(InputStream in) throws IOException {
sourceStream = in;
// 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;
}

@ -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,7 +53,7 @@ public class OGGLoader implements AssetLoader {
// private static int BLOCK_SIZE = 4096*64;
private UncachedOggStream oggStream;
private PhysicalOggStream oggStream;
private LogicalOggStream loStream;
private VorbisStream vorbisStream;
@ -106,6 +107,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();
@ -177,8 +224,6 @@ public class OGGLoader implements AssetLoader {
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;
}
}

@ -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();
}

Loading…
Cancel
Save