Fixed mp3 playing, refactored to avoid duplicating code.

git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@153 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
Andrzej Kapolka
2007-02-09 02:10:32 +00:00
parent ea9f4d481e
commit fc5459fb7d
7 changed files with 341 additions and 216 deletions
@@ -1,5 +1,5 @@
// //
// $Id$ // $Id: OggFileStream.java 119 2007-01-24 00:22:12Z dhoover $
// //
// Narya library - tools for developing networked games // Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved // Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
@@ -21,6 +21,8 @@
package com.threerings.openal; package com.threerings.openal;
import java.util.HashMap;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
@@ -32,25 +34,31 @@ import java.util.ArrayList;
import org.lwjgl.openal.AL10; import org.lwjgl.openal.AL10;
import com.jmex.sound.openAL.objects.util.OggInputStream;
/** /**
* An audio stream read from one or more Ogg Vorbis files. * An audio stream read from one or more files.
*/ */
public class OggFileStream extends Stream public class FileStream extends Stream
{ {
/** /**
* Creates a new Ogg stream for the specified file. * Registers a class of {@link StreamDecoder} for the specified file extension.
*/
public static void registerExtension (String extension, Class clazz)
{
_extensions.put(extension, clazz);
}
/**
* Creates a new stream for the specified file.
* *
* @param loop whether or not to play the file in a continuous loop * @param loop whether or not to play the file in a continuous loop
* if there's nothing on the queue * if there's nothing on the queue
*/ */
public OggFileStream (SoundManager soundmgr, File file, boolean loop) public FileStream (SoundManager soundmgr, File file, boolean loop)
throws IOException throws IOException
{ {
super(soundmgr); super(soundmgr);
_file = new OggFile(file, loop); _file = new QueuedFile(file, loop);
_istream = new OggInputStream(new FileInputStream(file)); _decoder = createDecoder(file);
} }
/** /**
@@ -61,49 +69,78 @@ public class OggFileStream extends Stream
*/ */
public void queueFile (File file, boolean loop) public void queueFile (File file, boolean loop)
{ {
_queue.add(new OggFile(file, loop)); _queue.add(new QueuedFile(file, loop));
} }
// documentation inherited // documentation inherited
protected int getFormat () protected int getFormat ()
{ {
return (_istream.getFormat() == OggInputStream.FORMAT_MONO16) ? return _decoder.getFormat();
AL10.AL_FORMAT_MONO16 : AL10.AL_FORMAT_STEREO16;
} }
// documentation inherited // documentation inherited
protected int getFrequency () protected int getFrequency ()
{ {
return _istream.getRate(); return _decoder.getFrequency();
} }
// documentation inherited // documentation inherited
protected int populateBuffer (ByteBuffer buf) protected int populateBuffer (ByteBuffer buf)
throws IOException throws IOException
{ {
int read = _istream.read(buf, buf.position(), buf.remaining()); int read = _decoder.read(buf);
while (buf.hasRemaining() && (!_queue.isEmpty() || _file.loop)) { while (buf.hasRemaining() && (!_queue.isEmpty() || _file.loop)) {
if (!_queue.isEmpty()) { if (!_queue.isEmpty()) {
_file = _queue.remove(0); _file = _queue.remove(0);
} }
_istream = new OggInputStream(new FileInputStream(_file.file)); _decoder = createDecoder(_file.file);
read = Math.max(0, read); read = Math.max(0, read);
read += _istream.read(buf, buf.position(), buf.remaining()); read += _decoder.read(buf);
} }
return read; return read;
} }
/** The file currently being played. */ /**
protected OggFile _file; * Creates and initializes a stream decoder for the specified file.
*/
protected StreamDecoder createDecoder (File file)
throws IOException
{
String path = file.getPath();
int idx = path.lastIndexOf('.');
if (idx == -1) {
Log.warning("Missing extension for file [file=" + path + "].");
return null;
}
String extension = path.substring(idx+1);
Class clazz = _extensions.get(extension);
if (clazz == null) {
Log.warning("No decoder registered for extension [extension=" + extension +
", file=" + path + "].");
return null;
}
StreamDecoder decoder;
try {
decoder = (StreamDecoder)clazz.newInstance();
} catch (Exception e) {
Log.warning("Error instantiating decoder [file=" + path + ", error=" + e + "].");
return null;
}
decoder.init(new FileInputStream(file));
return decoder;
}
/** The underlying Ogg input stream for the current file. */ /** The file currently being played. */
protected OggInputStream _istream; protected QueuedFile _file;
/** The stream decoder for the current file. */
protected StreamDecoder _decoder;
/** The queue of files to play after the current one. */ /** The queue of files to play after the current one. */
protected ArrayList<OggFile> _queue = new ArrayList<OggFile>(); protected ArrayList<QueuedFile> _queue = new ArrayList<QueuedFile>();
/** A file queued for play. */ /** A file queued for play. */
protected class OggFile protected class QueuedFile
{ {
/** The file to play. */ /** The file to play. */
public File file; public File file;
@@ -112,10 +149,17 @@ public class OggFileStream extends Stream
* in the queue. */ * in the queue. */
public boolean loop; public boolean loop;
public OggFile (File file, boolean loop) public QueuedFile (File file, boolean loop)
{ {
this.file = file; this.file = file;
this.loop = loop; this.loop = loop;
} }
} }
/** Registered decoder classes. */
protected static HashMap<String, Class> _extensions = new HashMap<String, Class>();
static {
registerExtension("ogg", OggStreamDecoder.class);
registerExtension("mp3", Mp3StreamDecoder.class);
}
} }
@@ -1,174 +0,0 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/nenya/
//
// 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.nio.ByteBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import org.lwjgl.openal.AL10;
import javazoom.jl.decoder.Bitstream;
import javazoom.jl.decoder.Decoder;
import javazoom.jl.decoder.Header;
import javazoom.jl.decoder.JavaLayerException;
import javazoom.jl.decoder.SampleBuffer;
/**
* An audio stream read from one or more mp3 files.
*/
public class Mp3FileStream extends Stream
{
/**
* Creates a new stream for the specified file.
*
* @param loop whether or not to play the file in a continuous loop if there's nothing on the
* queue
*/
public Mp3FileStream (SoundManager soundmgr, File file, boolean loop)
throws IOException
{
super(soundmgr);
_file = new Mp3File(file, loop);
_istream = new Bitstream(new FileInputStream(file));
_decoder = new Decoder();
}
/**
* Adds a file to the queue of files to play.
*
* @param loop if true, play this file in a loop if there's nothing else on the queue
*/
public void queueFile (File file, boolean loop)
{
_queue.add(new Mp3File(file, loop));
}
@Override // from Stream
public void dispose ()
{
super.dispose();
// TODO: close our bitstream...
}
// documentation inherited
protected int getFormat ()
{
return AL10.AL_FORMAT_STEREO16;
}
// documentation inherited
protected int getFrequency ()
{
return _frequency;
}
// documentation inherited
protected int populateBuffer (ByteBuffer buf)
throws IOException
{
if (_buffer == null) {
try {
Header h = _istream.readFrame();
for (int ii = 0; ii < 10 && (h == null); ii++) {
_istream.close();
Mp3File file = getNextFile();
if (file == null) {
return 0;
}
_istream = new Bitstream(new FileInputStream(file.file));
h = _istream.readFrame();
}
_buffer = (SampleBuffer)_decoder.decodeFrame(h, _istream);
_frequency = _buffer.getSampleFrequency();
} catch (JavaLayerException jle) {
throw (IOException)new IOException("JavaLayer decoding error").initCause(jle);
}
}
int srem = buf.remaining();
ShortBuffer sbuf = buf.asShortBuffer();
int wrote = Math.min(_buffer.getBufferLength()-_offset, sbuf.remaining());
sbuf.put(_buffer.getBuffer(), _offset, wrote);
if (_offset + wrote >= _buffer.getBufferLength()) {
_buffer = null;
_offset = 0;
_istream.closeFrame();
}
return 2 * wrote;
}
protected Mp3File getNextFile ()
{
if (_file.loop) {
return _file;
} else if (!_queue.isEmpty()) {
return _queue.remove(0);
} else {
return null;
}
}
/** The file currently being played. */
protected Mp3File _file;
/** Handles reading the mp3 data. */
protected Bitstream _istream;
/** Handles decoding the mp3 data. */
protected Decoder _decoder;
/** The currently decoded frame, if any. */
protected SampleBuffer _buffer;
/** Our offset into the currently decoded frame. */
protected int _offset;
/** The frequency of the most recently processed frame. */
protected int _frequency;
/** The queue of files to play after the current one. */
protected ArrayList<Mp3File> _queue = new ArrayList<Mp3File>();
/** A file queued for play. */
protected class Mp3File
{
/** The file to play. */
public File file;
/** Whether or not to play the file in a loop when there's nothing in the queue. */
public boolean loop;
public Mp3File (File file, boolean loop) {
this.file = file;
this.loop = loop;
}
}
}
@@ -0,0 +1,111 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/nenya/
//
// 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.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ShortBuffer;
import org.lwjgl.openal.AL10;
import javazoom.jl.decoder.Bitstream;
import javazoom.jl.decoder.Decoder;
import javazoom.jl.decoder.Header;
import javazoom.jl.decoder.JavaLayerException;
import javazoom.jl.decoder.SampleBuffer;
/**
* Decodes MP3 streams.
*/
public class Mp3StreamDecoder
implements StreamDecoder
{
// documentation inherited
public void init (InputStream in)
throws IOException
{
_istream = new Bitstream(in);
try {
_header = _istream.readFrame();
} catch (JavaLayerException e) {
throw new IOException(e.toString());
}
_decoder = new Decoder();
}
// documentation inherited
public int getFormat ()
{
return AL10.AL_FORMAT_STEREO16;
}
// documentation inherited
public int getFrequency ()
{
return _header.frequency();
}
// documentation inherited
public int read (ByteBuffer buf)
throws IOException
{
ShortBuffer sbuf = buf.asShortBuffer();
int total = 0;
while (sbuf.hasRemaining() && _header != null) {
if (_buffer == null) {
try {
_buffer = (SampleBuffer)_decoder.decodeFrame(_header, _istream);
_istream.closeFrame();
_header = _istream.readFrame();
} catch (JavaLayerException e) {
throw new IOException(e.toString());
}
}
int blen = _buffer.getBufferLength(),
length = Math.min(sbuf.remaining(), blen - _offset);
sbuf.put(_buffer.getBuffer(), _offset, length);
if ((_offset += length) >= blen) {
_offset = 0;
_buffer = null;
}
total += (length * 2);
}
buf.position(buf.position() + total);
return total;
}
/** Handles reading the mp3 data. */
protected Bitstream _istream;
/** The mp3 header. */
protected Header _header;
/** Handles decoding the mp3 data. */
protected Decoder _decoder;
/** The sample buffer used for output. */
protected SampleBuffer _buffer;
/** Our offset into the currently decoded frame. */
protected int _offset;
}
@@ -0,0 +1,68 @@
//
// $Id: OggFileStream.java 119 2007-01-24 00:22:12Z dhoover $
//
// 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.io.InputStream;
import java.nio.ByteBuffer;
import org.lwjgl.openal.AL10;
import com.jmex.sound.openAL.objects.util.OggInputStream;
/**
* Decodes Ogg Vorbis streams.
*/
public class OggStreamDecoder
implements StreamDecoder
{
// documentation inherited
public void init (InputStream in)
throws IOException
{
_istream = new OggInputStream(in);
}
// documentation inherited
public int getFormat ()
{
return (_istream.getFormat() == OggInputStream.FORMAT_MONO16) ?
AL10.AL_FORMAT_MONO16 : AL10.AL_FORMAT_STEREO16;
}
// documentation inherited
public int getFrequency ()
{
return _istream.getRate();
}
// documentation inherited
public int read (ByteBuffer buf)
throws IOException
{
return _istream.read(buf, buf.position(), buf.remaining());
}
/** The underlying Ogg input stream. */
protected OggInputStream _istream;
}
+2 -1
View File
@@ -24,6 +24,7 @@ package com.threerings.openal;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer; import java.nio.IntBuffer;
import org.lwjgl.BufferUtils; import org.lwjgl.BufferUtils;
@@ -263,7 +264,7 @@ public abstract class Stream
protected boolean populateBuffer (int bufferId) protected boolean populateBuffer (int bufferId)
{ {
if (_abuf == null) { if (_abuf == null) {
_abuf = ByteBuffer.allocateDirect(BUFFER_SIZE); _abuf = ByteBuffer.allocateDirect(BUFFER_SIZE).order(ByteOrder.nativeOrder());
} }
_abuf.clear(); _abuf.clear();
int read = 0; int read = 0;
@@ -0,0 +1,58 @@
//
// $Id: OggFileStream.java 119 2007-01-24 00:22:12Z dhoover $
//
// 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.io.InputStream;
import java.nio.ByteBuffer;
/**
* Decodes audio streams from data read from an {@link InputStream}.
*/
public interface StreamDecoder
{
/**
* Initializes the decoder with its input stream.
*/
public abstract void init (InputStream in)
throws IOException;
/**
* Returns the sound format (see {@link Stream#getFormat}).
*/
public abstract int getFormat ();
/**
* Returns the sound frequency (see {@link Stream#getFrequency}).
*/
public abstract int getFrequency ();
/**
* Reads as much data as will fit into the specified buffer.
*
* @return the number of bytes read. If less than or equal to zero, the decoder has reached
* the end of the stream.
*/
public abstract int read (ByteBuffer buf)
throws IOException;
}
@@ -3,6 +3,7 @@
package com.threerings.openal; package com.threerings.openal;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import com.samskivert.util.Interval; import com.samskivert.util.Interval;
@@ -17,7 +18,7 @@ public class TestSoundManager
public static void main (String[] args) public static void main (String[] args)
{ {
if (args.length == 0) { if (args.length == 0) {
System.err.println("Usage: TestSoundManager sound.wav"); System.err.println("Usage: TestSoundManager sound.[wav|mp3|ogg]");
System.exit(-1); System.exit(-1);
} }
@@ -31,19 +32,35 @@ public class TestSoundManager
} }
}; };
SoundManager smgr = SoundManager.createSoundManager(rqueue); final SoundManager smgr = SoundManager.createSoundManager(rqueue);
ClipProvider provider = new WaveDataClipProvider(); ClipProvider provider = new WaveDataClipProvider();
final SoundGroup group = smgr.createGroup(provider, 5); final SoundGroup group = smgr.createGroup(provider, 5);
final String path = args[0]; final String path = args[0];
// queue up an interval to play a sound over and over String lpath = path.toLowerCase();
Interval i = new Interval(rqueue) { if (lpath.endsWith("mp3") || lpath.endsWith(".ogg")) {
public void expired () { // play the mp3/ogg file in a loop
Sound sound = group.getSound(path); try {
sound.play(true); new FileStream(smgr, new File(args[0]), true).play();
} catch (IOException e) {
e.printStackTrace();
} }
}; Interval i = new Interval(rqueue) {
i.schedule(100L, true); public void expired () {
smgr.updateStreams(0.1f);
}
};
i.schedule(100L, true);
} else {
// queue up an interval to play a sound over and over
Interval i = new Interval(rqueue) {
public void expired () {
Sound sound = group.getSound(path);
sound.play(true);
}
};
i.schedule(100L, true);
}
while (true) { while (true) {
Runnable r = (Runnable)_queue.get(); Runnable r = (Runnable)_queue.get();