/*
* Copyright (c) 2003-onwards Shaven Puppy Ltd
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'Shaven Puppy' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.shavenpuppy.jglib.openal;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import org.lwjgl.BufferUtils;
import com.shavenpuppy.jglib.Resource;
import com.shavenpuppy.jglib.Resources;
import com.shavenpuppy.jglib.resources.WaveWrapper;
import static org.lwjgl.openal.AL10.*;
/**
* $Id: ALStreamInstance.java,v 1.29 2011/10/03 15:08:19 cix_foo Exp $
*
* @author $Author: cix_foo $
* @version $Revision: 1.29 $
*/
public class ALStreamInstance extends Resource {
private static final long serialVersionUID = 1L;
private static final int BUFSIZE = 65536;
private static final int BUFFERS = 6;
private static int num;
/*
* Transient data
*/
private transient ALStream sourceStream;
/** The url */
private transient String url;
/** The buffers */
private transient ALBuffer buffers[];
private transient int writeBuf;
/** Input stream */
private transient InputStream inputStream;
/** Buffer */
private transient byte[] buf;
/** Upload buffer */
private transient ByteBuffer byteBuf;
/** Current buffer read pos */
private transient int pos;
private transient int type;
private transient int frequency;
private transient int format;
private transient int numQueued;
private transient boolean waitForQueue, playing, startPlaying;
private transient boolean looped;
/** Samples played (roughly) */
private transient int samplesPlayed;
/** Size of a sample (1,2,4,8) */
private transient int sampleSize;
private ALSource owner;
/**
* C'tor
*/
ALStreamInstance(ALStream sourceStream) {
super("StreamInstance "+(num++));
init(sourceStream);
}
void init(ALStream sourceStream) {
this.sourceStream = sourceStream;
this.url = sourceStream.getURL();
this.inputStream = sourceStream.getInputStream();
this.format = sourceStream.getFormat();
// We'll either have a URL or an InputStream
assert url == null && inputStream != null || url != null && inputStream == null;
this.looped = sourceStream.isLooped();
this.samplesPlayed = 0;
}
/**
* @return Returns the sourceStream.
*/
public ALStream getSourceStream() {
return sourceStream;
}
synchronized void setOwner(ALSource newOwner) {
if (owner != newOwner) {
setPlaying(false);
}
owner = newOwner;
}
/**
* @return Returns the owner.
*/
public ALSource getOwner() {
return owner;
}
/**
* @return Returns the type.
*/
public int getType() {
return type;
}
/**
* @return the format
*/
public int getFormat() {
return format;
}
/**
* @see com.shavenpuppy.jglib.Resource#doCreate()
*/
@Override
protected void doCreate() {
if (!org.lwjgl.openal.AL.isCreated()) {
return;
}
try {
// 1. Generate buffers
buffers = new ALBuffer[BUFFERS];
for (int i = 0; i < buffers.length; i ++) {
buffers[i] = new ALBuffer("StreamBuffer "+i+"["+this+"]");
buffers[i].create();
}
buf = new byte[BUFSIZE];
byteBuf = BufferUtils.createByteBuffer(BUFSIZE);
initStream();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void initStream() throws Exception {
// First get the wave if necessary
if (url != null) {
if (url.startsWith("resource:")) {
// Load directly from Resources
WaveWrapper waveResource = (WaveWrapper) Resources.get(url.substring(9));
if (waveResource == null) {
throw new RuntimeException("Resource "+url+" not found");
}
inputStream = null;
inputStream = waveResource.getStream();
type = waveResource.getType();
frequency = waveResource.getFrequency();
}
}
if (inputStream == null) {
throw new Exception("No input stream specified for "+this);
}
format = AL.translateFormat(type);
switch (format) {
case AL_FORMAT_MONO8:
sampleSize = 1;
break;
case AL_FORMAT_STEREO8:
case AL_FORMAT_MONO16:
sampleSize = 2;
break;
case AL_FORMAT_STEREO16:
sampleSize = 4;
break;
default:
assert false;
}
}
/**
* @see com.shavenpuppy.jglib.Resource#doDestroy()
*/
@Override
protected void doDestroy() {
if (buffers != null) {
for (int i = 0; i < buffers.length; i ++) {
buffers[i].destroy();
}
buffers = null;
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace(System.err);
}
inputStream = null;
}
buf = null;
byteBuf = null;
}
/**
* @return Returns the playing.
*/
public synchronized boolean isPlaying() {
return playing;
}
/**
* @param playing The playing to set.
*/
public synchronized void setPlaying(boolean playing) {
if (this.playing == playing) {
return;
}
this.playing = playing;
if (!playing) {
numQueued = 0;
waitForQueue = false;
startPlaying = false;
writeBuf = 0;
owner.stop();
// Dequeue everything
int queued;
do {
queued = alGetSourcei(owner.getSourceID(), AL_BUFFERS_QUEUED);
if (queued > 0) {
owner.dequeue(queued);
}
} while (queued > 0);
int processed;
do {
processed = alGetSourcei(owner.getSourceID(), AL_BUFFERS_PROCESSED);
if (processed > 0) {
AL.scratch.ints.clear();
AL.scratch.ints.limit(processed);
alSourceUnqueueBuffers(owner.getSourceID(), AL.scratch.ints);
}
} while (processed > 0);
} else {
startPlaying = true;
}
notifyAll();
}
/**
* @return the number of samples played (roughly).
*/
public int getSamplesPlayed() {
return samplesPlayed;
}
void reset() throws Exception {
if (url != null && inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
}
inputStream = null;
}
initStream();
}
/**
* Tick. This keeps our buffers full. This is run in a separate thread.
*/
public synchronized void tick() throws Exception {
if (!buffers[writeBuf].isCreated()) {
// Wait for resource to be created...
return;
}
if (!isPlaying()) {
return;
}
if (waitForQueue) {
int processed = alGetSourcei(owner.getSourceID(), AL_BUFFERS_PROCESSED);
if (processed <= 0) {
// Have to wait..
return;
}
owner.dequeue(processed);
numQueued -= processed;
waitForQueue = false;
}
// We are now on the second buffer; this means we can unqueue the first, and
// queue the buffer that's been filled; and start decoding the next buffer.
int read = inputStream.read(buf, pos, buf.length - pos);
if (read == -1) {
if (looped) {
initStream();
tick();
} else {
if (numQueued == 0) {
setPlaying(false);
} else {
waitForQueue = true;
}
}
} else {
pos += read;
if (pos == buf.length) {
pos = 0;
// We're full. Upload to AL. We'll count the samples as being played.
samplesPlayed += buf.length / sampleSize;
byteBuf.put(buf);
byteBuf.flip();
final ALBuffer bufToQueue = buffers[writeBuf ++];
if (writeBuf == buffers.length) {
writeBuf = 0;
}
numQueued ++;
if (numQueued == buffers.length) {
waitForQueue = true;
}
if (startPlaying) {
owner.unattach(); // Ensure source has no buffers in it of the wrong format
startPlaying = false;
}
// Ensure that the buffer is not somehow inexplicably already queued
alBufferData(bufToQueue.getBufferID(), format, byteBuf, frequency);
owner.queue(bufToQueue);
if (owner.getInt(AL_SOURCE_STATE) != AL_PLAYING) {
owner.play();
}
byteBuf.clear();
}
}
}
}