Path: blob/master/SLICK_HOME/src/org/newdawn/slick/openal/OpenALStreamPlayer.java
1461 views
package org.newdawn.slick.openal;12import java.io.IOException;3import java.net.URL;4import java.nio.ByteBuffer;5import java.nio.IntBuffer;67import org.lwjgl.BufferUtils;8import org.lwjgl.openal.AL10;9import org.lwjgl.openal.AL11;10import org.lwjgl.openal.OpenALException;11import org.newdawn.slick.util.Log;12import org.newdawn.slick.util.ResourceLoader;1314/**15* A generic tool to work on a supplied stream, pulling out PCM data and buffered it to OpenAL16* as required.17*18* @author Kevin Glass19* @author Nathan Sweet <[email protected]>20* @author Rockstar play and setPosition cleanup21*/22public class OpenALStreamPlayer {23/** The number of buffers to maintain */24public static final int BUFFER_COUNT = 3;25/** The size of the sections to stream from the stream */26private static final int sectionSize = 4096 * 20;2728/** The buffer read from the data stream */29private byte[] buffer = new byte[sectionSize];30/** Holds the OpenAL buffer names */31private IntBuffer bufferNames;32/** The byte buffer passed to OpenAL containing the section */33private ByteBuffer bufferData = BufferUtils.createByteBuffer(sectionSize);34/** The buffer holding the names of the OpenAL buffer thats been fully played back */35private IntBuffer unqueued = BufferUtils.createIntBuffer(1);36/** The source we're playing back on */37private int source;38/** The number of buffers remaining */39private int remainingBufferCount;40/** True if we should loop the track */41private boolean loop;42/** True if we've completed play back */43private boolean done = true;44/** The stream we're currently reading from */45private AudioInputStream audio;46/** The source of the data */47private String ref;48/** The source of the data */49private URL url;50/** The pitch of the music */51private float pitch;52/** Position in seconds of the previously played buffers */53private float positionOffset;5455/**56* Create a new player to work on an audio stream57*58* @param source The source on which we'll play the audio59* @param ref A reference to the audio file to stream60*/61public OpenALStreamPlayer(int source, String ref) {62this.source = source;63this.ref = ref;6465bufferNames = BufferUtils.createIntBuffer(BUFFER_COUNT);66AL10.alGenBuffers(bufferNames);67}6869/**70* Create a new player to work on an audio stream71*72* @param source The source on which we'll play the audio73* @param url A reference to the audio file to stream74*/75public OpenALStreamPlayer(int source, URL url) {76this.source = source;77this.url = url;7879bufferNames = BufferUtils.createIntBuffer(BUFFER_COUNT);80AL10.alGenBuffers(bufferNames);81}8283/**84* Initialise our connection to the underlying resource85*86* @throws IOException Indicates a failure to open the underling resource87*/88private void initStreams() throws IOException {89if (audio != null) {90audio.close();91}9293OggInputStream audio;9495if (url != null) {96audio = new OggInputStream(url.openStream());97} else {98audio = new OggInputStream(ResourceLoader.getResourceAsStream(ref));99}100101this.audio = audio;102positionOffset = 0;103}104105/**106* Get the source of this stream107*108* @return The name of the source of string109*/110public String getSource() {111return (url == null) ? ref : url.toString();112}113114/**115* Clean up the buffers applied to the sound source116*/117private void removeBuffers() {118IntBuffer buffer = BufferUtils.createIntBuffer(1);119int queued = AL10.alGetSourcei(source, AL10.AL_BUFFERS_QUEUED);120121while (queued > 0)122{123AL10.alSourceUnqueueBuffers(source, buffer);124queued--;125}126}127128/**129* Start this stream playing130*131* @param loop True if the stream should loop132* @throws IOException Indicates a failure to read from the stream133*/134public void play(boolean loop) throws IOException {135this.loop = loop;136initStreams();137138done = false;139140AL10.alSourceStop(source);141removeBuffers();142143startPlayback();144}145146/**147* Setup the playback properties148*149* @param pitch The pitch to play back at150*/151public void setup(float pitch) {152this.pitch = pitch;153}154155/**156* Check if the playback is complete. Note this will never157* return true if we're looping158*159* @return True if we're looping160*/161public boolean done() {162return done;163}164165/**166* Poll the bufferNames - check if we need to fill the bufferNames with another167* section.168*169* Most of the time this should be reasonably quick170*/171public void update() {172if (done) {173return;174}175176float sampleRate = audio.getRate();177float sampleSize;178if (audio.getChannels() > 1) {179sampleSize = 4; // AL10.AL_FORMAT_STEREO16180} else {181sampleSize = 2; // AL10.AL_FORMAT_MONO16182}183184int processed = AL10.alGetSourcei(source, AL10.AL_BUFFERS_PROCESSED);185while (processed > 0) {186unqueued.clear();187AL10.alSourceUnqueueBuffers(source, unqueued);188189int bufferIndex = unqueued.get(0);190191float bufferLength = (AL10.alGetBufferi(bufferIndex, AL10.AL_SIZE) / sampleSize) / sampleRate;192positionOffset += bufferLength;193194if (stream(bufferIndex)) {195AL10.alSourceQueueBuffers(source, unqueued);196} else {197remainingBufferCount--;198if (remainingBufferCount == 0) {199done = true;200}201}202processed--;203}204205int state = AL10.alGetSourcei(source, AL10.AL_SOURCE_STATE);206207if (state != AL10.AL_PLAYING) {208AL10.alSourcePlay(source);209}210}211212/**213* Stream some data from the audio stream to the buffer indicates by the ID214*215* @param bufferId The ID of the buffer to fill216* @return True if another section was available217*/218public boolean stream(int bufferId) {219try {220int count = audio.read(buffer);221222if (count != -1) {223bufferData.clear();224bufferData.put(buffer,0,count);225bufferData.flip();226227int format = audio.getChannels() > 1 ? AL10.AL_FORMAT_STEREO16 : AL10.AL_FORMAT_MONO16;228try {229AL10.alBufferData(bufferId, format, bufferData, audio.getRate());230} catch (OpenALException e) {231Log.error("Failed to loop buffer: "+bufferId+" "+format+" "+count+" "+audio.getRate(), e);232return false;233}234} else {235if (loop) {236initStreams();237stream(bufferId);238} else {239done = true;240return false;241}242}243244return true;245} catch (IOException e) {246Log.error(e);247return false;248}249}250251/**252* Seeks to a position in the music.253*254* @param position Position in seconds.255* @return True if the setting of the position was successful256*/257public boolean setPosition(float position) {258try {259if (getPosition() > position) {260initStreams();261}262263float sampleRate = audio.getRate();264float sampleSize;265if (audio.getChannels() > 1) {266sampleSize = 4; // AL10.AL_FORMAT_STEREO16267} else {268sampleSize = 2; // AL10.AL_FORMAT_MONO16269}270271while (positionOffset < position) {272int count = audio.read(buffer);273if (count != -1) {274float bufferLength = (count / sampleSize) / sampleRate;275positionOffset += bufferLength;276} else {277if (loop) {278initStreams();279} else {280done = true;281}282return false;283}284}285286startPlayback();287288return true;289} catch (IOException e) {290Log.error(e);291return false;292}293}294295/**296* Starts the streaming.297*/298private void startPlayback() {299AL10.alSourcei(source, AL10.AL_LOOPING, AL10.AL_FALSE);300AL10.alSourcef(source, AL10.AL_PITCH, pitch);301302remainingBufferCount = BUFFER_COUNT;303304for (int i = 0; i < BUFFER_COUNT; i++) {305stream(bufferNames.get(i));306}307308AL10.alSourceQueueBuffers(source, bufferNames);309AL10.alSourcePlay(source);310}311312/**313* Return the current playing position in the sound314*315* @return The current position in seconds.316*/317public float getPosition() {318return positionOffset + AL10.alGetSourcef(source, AL11.AL_SEC_OFFSET);319}320}321322323324