* OGG loader now supports stream-cache feature
git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@7516 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
This commit is contained in:
parent
3a938b7dc9
commit
f1361cc9e6
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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…
x
Reference in New Issue
Block a user