* 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;
|
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 int pageNumber;
|
private boolean bos = false;
|
||||||
|
private InputStream sourceStream;
|
||||||
private HashMap<Integer, LogicalOggStream> logicalStreams
|
private HashMap<Integer, LogicalOggStream> logicalStreams
|
||||||
= new HashMap<Integer, LogicalOggStream>();
|
= new HashMap<Integer, LogicalOggStream>();
|
||||||
|
|
||||||
/**
|
private IntMap<OggPage> oggPages = new IntMap<OggPage>();
|
||||||
* Creates an instance of this class, using the specified file as cache. The
|
private OggPage lastPage;
|
||||||
* 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];
|
private int pageNumber;
|
||||||
sourceStream = stream;
|
|
||||||
|
|
||||||
|
public CachedOggStream(InputStream in) throws IOException {
|
||||||
|
sourceStream = in;
|
||||||
|
|
||||||
|
// Read all OGG pages in file
|
||||||
|
long time = System.nanoTime();
|
||||||
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,7 +53,7 @@ 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;
|
||||||
|
|
||||||
@ -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{
|
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 numSamples = (int) oggStream.getLastOggPage().getAbsoluteGranulePosition();
|
|
||||||
|
|
||||||
// Number of Samples * Number of Channels * Bytes Per Sample
|
int bytesToCopy = getOggTotalBytes( dataBytes.length );
|
||||||
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();
|
||||||
|
if (readStream && streamCache){
|
||||||
|
oggStream = new CachedOggStream(in);
|
||||||
|
}else{
|
||||||
oggStream = new UncachedOggStream(in);
|
oggStream = new UncachedOggStream(in);
|
||||||
|
}
|
||||||
|
|
||||||
Collection<LogicalOggStream> streams = oggStream.getLogicalStreams();
|
Collection<LogicalOggStream> streams = oggStream.getLogicalStreams();
|
||||||
loStream = streams.iterator().next();
|
loStream = streams.iterator().next();
|
||||||
@ -177,8 +224,6 @@ public class OGGLoader implements AssetLoader {
|
|||||||
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){
|
||||||
|
// 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();
|
readNextOggPage();
|
||||||
}
|
}
|
||||||
|
// }
|
||||||
|
|
||||||
return pageCache.removeFirst();
|
return pageCache.removeFirst();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user