From e732fe48aa17171d3308819f094f80e07f4ebfbd Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 10 May 2006 19:47:40 +0000 Subject: [PATCH] Added support for streaming Ogg files. Cross-fading still to come. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4107 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- build.xml | 3 + lib/LIBS | 1 + .../com/threerings/openal/OggFileStream.java | 89 ++++++ .../com/threerings/openal/SoundManager.java | 35 +++ src/java/com/threerings/openal/Stream.java | 272 ++++++++++++++++++ 5 files changed, 400 insertions(+) create mode 100644 src/java/com/threerings/openal/OggFileStream.java create mode 100644 src/java/com/threerings/openal/Stream.java diff --git a/build.xml b/build.xml index e0368558c..8b6b3921e 100644 --- a/build.xml +++ b/build.xml @@ -97,6 +97,8 @@ classname="com.jme.scene.Node" classpathref="classpath"/> + @@ -106,6 +108,7 @@ + diff --git a/lib/LIBS b/lib/LIBS index df43b1484..339f58471 100644 --- a/lib/LIBS +++ b/lib/LIBS @@ -8,6 +8,7 @@ commons-logging.jar jme-awt.jar jme-bui.jar jme-model.jar +jme-sound.jar jme.jar junit-3.7.jar lwjgl.jar diff --git a/src/java/com/threerings/openal/OggFileStream.java b/src/java/com/threerings/openal/OggFileStream.java new file mode 100644 index 000000000..ff46de23f --- /dev/null +++ b/src/java/com/threerings/openal/OggFileStream.java @@ -0,0 +1,89 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.openal; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import java.nio.ByteBuffer; + +import org.lwjgl.openal.AL10; + +import com.jmex.sound.openAL.objects.util.OggInputStream; + +/** + * An audio stream read from an Ogg Vorbis file. + */ +public class OggFileStream extends Stream +{ + /** + * Creates a new Ogg stream for the specified Ogg file. + * + * @param loop whether or not to play the file in a continuous loop + */ + public OggFileStream (SoundManager soundmgr, File file, boolean loop) + throws IOException + { + super(soundmgr); + _file = file; + _istream = new OggInputStream(new FileInputStream(_file)); + _loop = loop; + } + + // documentation inherited + protected int getFormat () + { + return (_istream.getFormat() == OggInputStream.FORMAT_MONO16) ? + AL10.AL_FORMAT_MONO16 : AL10.AL_FORMAT_STEREO16; + } + + // documentation inherited + protected int getFrequency () + { + return _istream.getRate(); + } + + // documentation inherited + protected int populateBuffer (ByteBuffer buf) + throws IOException + { + int read = _istream.read(buf, buf.position(), buf.remaining()); + while (buf.hasRemaining() && _loop) { + _istream = new OggInputStream(new FileInputStream(_file)); + read = Math.max(0, read); + read += _istream.read(buf, buf.position(), buf.remaining()); + } + return read; + } + + /** The Ogg file from which to read. */ + protected File _file; + + /** The underlying Ogg input stream. */ + protected OggInputStream _istream; + + /** Whether or not to loop back to the beginning of the file when we've + * reached the end. */ + protected boolean _loop; +} diff --git a/src/java/com/threerings/openal/SoundManager.java b/src/java/com/threerings/openal/SoundManager.java index 22b09497c..a74ded411 100644 --- a/src/java/com/threerings/openal/SoundManager.java +++ b/src/java/com/threerings/openal/SoundManager.java @@ -22,6 +22,7 @@ package com.threerings.openal; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; import java.util.WeakHashMap; @@ -103,6 +104,19 @@ public class SoundManager return new SoundGroup(this, provider, sources); } + /** + * Updates all of the streams controlled by the manager. This should be + * called once per frame by the application. + * + * @param time the number of seconds elapsed since the last update + */ + public void updateStreams (float time) + { + for (Stream stream : _streams) { + stream.update(time); + } + } + /** * Creates a sound manager and initializes the OpenAL sound subsystem. */ @@ -205,6 +219,24 @@ public class SoundManager }); } + /** + * Adds a stream to the list maintained by the manager. Called by streams + * when they are created. + */ + protected void addStream (Stream stream) + { + _streams.add(stream); + } + + /** + * Removes a stream from the list maintained by the manager. Called by + * streams when they are disposed. + */ + protected void removeStream (Stream stream) + { + _streams.remove(stream); + } + /** The thread that loads up sound clips in the background. */ protected Thread _loader = new Thread("SoundManager.Loader") { public void run () { @@ -254,6 +286,9 @@ public class SoundManager /** Contains a queue of clip buffers waiting to be loaded. */ protected Queue _toLoad; + /** The list of active streams. */ + protected ArrayList _streams = new ArrayList(); + /** The one and only sound manager, here for an exclusive performance * by special request. Available for all your sound playing needs. */ protected static SoundManager _soundmgr; diff --git a/src/java/com/threerings/openal/Stream.java b/src/java/com/threerings/openal/Stream.java new file mode 100644 index 000000000..6c309c1c7 --- /dev/null +++ b/src/java/com/threerings/openal/Stream.java @@ -0,0 +1,272 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.openal; + +import java.io.IOException; + +import java.nio.ByteBuffer; +import java.nio.IntBuffer; + +import org.lwjgl.BufferUtils; +import org.lwjgl.openal.AL10; + +/** + * Represents a streaming source of sound data. + */ +public abstract class Stream +{ + /** + * Creates a new stream. Call {@link #dispose} when finished with the + * stream. + * + * @param soundmgr a reference to the sound manager that will update the + * stream + */ + public Stream (SoundManager soundmgr) + { + _soundmgr = soundmgr; + + // create the source and buffers + _nbuf = BufferUtils.createIntBuffer(NUM_BUFFERS); + _nbuf.limit(1); + AL10.alGenSources(_nbuf); + _sourceId = _nbuf.get(); + _nbuf.clear(); + AL10.alGenBuffers(_nbuf); + _nbuf.get(_bufferIds); + + // register with sound manager + _soundmgr.addStream(this); + } + + /** + * Sets the base gain of the stream. + */ + public void setGain (float gain) + { + _gain = gain; + AL10.alSourcef(_sourceId, AL10.AL_GAIN, _gain); + } + + /** + * Sets the pitch of the stream. + */ + public void setPitch (float pitch) + { + _pitch = pitch; + AL10.alSourcef(_sourceId, AL10.AL_PITCH, _pitch); + } + + /** + * Starts playing this stream. + */ + public void play () + { + if (_state == AL10.AL_PLAYING) { + Log.warning("Tried to play stream already playing."); + return; + } + if (_state == -1) { + _qidx = _qlen = 0; + queueBuffers(NUM_BUFFERS); + } + AL10.alSourcePlay(_sourceId); + _state = AL10.AL_PLAYING; + } + + /** + * Pauses this stream. + */ + public void pause () + { + if (_state != AL10.AL_PLAYING) { + Log.warning("Tried to pause stream that wasn't playing."); + return; + } + AL10.alSourcePause(_sourceId); + _state = AL10.AL_PAUSED; + } + + /** + * Stops this stream. + */ + public void stop () + { + if (_state == AL10.AL_STOPPED) { + Log.warning("Tried to stop stream that was already stopped."); + return; + } + AL10.alSourceStop(_sourceId); + _state = AL10.AL_STOPPED; + } + + /** + * Releases the resources held by this stream and removes it from + * the manager. + */ + public void dispose () + { + // make sure the stream is stopped + if (_state != AL10.AL_STOPPED) { + stop(); + } + + // delete the source and buffers + _nbuf.clear(); + _nbuf.put(_sourceId).flip(); + AL10.alDeleteSources(_nbuf); + _nbuf.clear(); + _nbuf.put(_bufferIds).flip(); + AL10.alDeleteBuffers(_nbuf); + + // remove from manager + _soundmgr.removeStream(this); + } + + /** + * Updates the state of this stream, loading data into buffers and + * adjusting gain as necessary. Called periodically by the + * {@link SoundManager}. + * + * @param time the amount of time elapsed since the last update + */ + protected void update (float time) + { + if (_state != AL10.AL_PLAYING) { + return; + } + // find out how many buffers have been played + int played = AL10.alGetSourcei(_sourceId, AL10.AL_BUFFERS_PROCESSED); + if (played == 0) { + return; + } + _nbuf.clear(); + for (int ii = 0; ii < played; ii++) { + _nbuf.put(_bufferIds[_qidx]); + _qidx = (_qidx + 1) % NUM_BUFFERS; + _qlen--; + } + _nbuf.flip(); + AL10.alSourceUnqueueBuffers(_sourceId, _nbuf); + queueBuffers(played); + } + + /** + * Queues (up to) the specified number of buffers. + */ + protected void queueBuffers (int buffers) + { + _nbuf.clear(); + for (int ii = 0; ii < buffers; ii++) { + int bufferId = _bufferIds[(_qidx + _qlen) % NUM_BUFFERS]; + if (populateBuffer(bufferId)) { + _nbuf.put(bufferId); + _qlen++; + } else { + break; + } + } + _nbuf.flip(); + AL10.alSourceQueueBuffers(_sourceId, _nbuf); + } + + /** + * Populates the identified buffer with as much data as it can hold. + * + * @return true if data was read into the buffer and it should be enqueued, + * false if the end of the stream has been reached and no data was read + * into the buffer + */ + protected boolean populateBuffer (int bufferId) + { + if (_abuf == null) { + _abuf = ByteBuffer.allocateDirect(BUFFER_SIZE); + } + _abuf.clear(); + int read = 0; + try { + read = Math.max(populateBuffer(_abuf), 0); + } catch (IOException e) { + Log.warning("Error reading audio stream [error=" + e + "]."); + } + if (read <= 0) { + return false; + } + _abuf.rewind().limit(read); + AL10.alBufferData(bufferId, getFormat(), _abuf, getFrequency()); + return true; + } + + /** + * Returns the OpenAL audio format of the stream. + */ + protected abstract int getFormat (); + + /** + * Returns the stream's playback frequency in samples per second. + */ + protected abstract int getFrequency (); + + /** + * Populates the given buffer with audio data. + * + * @return the total number of bytes read into the buffer, or -1 if the + * end of the stream has been reached + */ + protected abstract int populateBuffer (ByteBuffer buf) + throws IOException; + + /** The manager to which the stream was added. */ + protected SoundManager _soundmgr; + + /** The source through which the stream plays. */ + protected int _sourceId; + + /** The buffers through which we cycle. */ + protected int[] _bufferIds = new int[NUM_BUFFERS]; + + /** The starting index and length of the current queue in + * {@link #_bufferIds}. */ + protected int _qidx, _qlen; + + /** The pitch of the stream. */ + protected float _pitch; + + /** The gain of the stream. */ + protected float _gain; + + /** The buffer used to store names. */ + protected IntBuffer _nbuf; + + /** The buffer used to store audio data temporarily. */ + protected ByteBuffer _abuf; + + /** The OpenAL state of the stream (AL_STOPPED, AL_PLAYING, AL_PAUSED, or + * -1 if uninitialized). */ + protected int _state = -1; + + /** The size of the buffers in bytes. */ + protected static final int BUFFER_SIZE = 131072; + + /** The number of buffers to use. */ + protected static final int NUM_BUFFERS = 4; +}