diff --git a/etc/depends-incl.xml b/etc/depends-incl.xml index 3386d057..780b7b93 100644 --- a/etc/depends-incl.xml +++ b/etc/depends-incl.xml @@ -17,9 +17,9 @@ classname="com.jmex.bui.BWindow" classpathref="classpath"/> - - + + @@ -39,7 +39,7 @@ - + diff --git a/etc/libs-incl.xml b/etc/libs-incl.xml index 06dfae4b..62da3bd3 100644 --- a/etc/libs-incl.xml +++ b/etc/libs-incl.xml @@ -27,6 +27,8 @@ + + diff --git a/src/java/com/threerings/openal/OggStreamDecoder.java b/src/java/com/threerings/openal/OggStreamDecoder.java index 25ca8ed7..a99bf9ec 100644 --- a/src/java/com/threerings/openal/OggStreamDecoder.java +++ b/src/java/com/threerings/openal/OggStreamDecoder.java @@ -25,43 +25,218 @@ import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; +import java.nio.ShortBuffer; import org.lwjgl.openal.AL10; -import com.jmex.sound.openAL.objects.util.OggInputStream; +import com.jcraft.jogg.Packet; +import com.jcraft.jogg.Page; +import com.jcraft.jogg.StreamState; +import com.jcraft.jogg.SyncState; +import com.jcraft.jorbis.Block; +import com.jcraft.jorbis.Comment; +import com.jcraft.jorbis.DspState; +import com.jcraft.jorbis.Info; /** * Decodes Ogg Vorbis streams. */ public class OggStreamDecoder extends StreamDecoder { + // documentation inherited public void init (InputStream in) throws IOException { - _istream = new OggInputStream(in); + _in = in; + _sync.init(); + _info.init(); + + // read the first packet "manually" to make sure everything's kosher + readChunk(); + if (_sync.pageout(_page) != 1) { + throw new IOException("Input is not an Ogg bitstream."); + } + _stream.init(_page.serialno()); + _stream.reset(); + + if (_stream.pagein(_page) < 0) { + throw new IOException("Error reading first page of Ogg bitstream data."); + } + if (_stream.packetout(_packet) != 1) { + throw new IOException("Error reading initial header packet."); + } + + Comment comment = new Comment(); + comment.init(); + if (_info.synthesis_headerin(comment, _packet) < 0) { + throw new IOException("Ogg bitstream does not contain Vorbis audio data."); + } + + // two more packets in header + for (int ii = 0; ii < 2; ii++) { + if (!readPacket()) { + throw new IOException("End of file before reading all Vorbis headers."); + } + _info.synthesis_headerin(comment, _packet); + } + _dsp.synthesis_init(_info); + _block.init(_dsp); + _offsets = new int[_info.channels]; } // documentation inherited public int getFormat () { - return (_istream.getFormat() == OggInputStream.FORMAT_MONO16) ? - AL10.AL_FORMAT_MONO16 : AL10.AL_FORMAT_STEREO16; + return (_info.channels == 1) ? AL10.AL_FORMAT_MONO16 : AL10.AL_FORMAT_STEREO16; } // documentation inherited public int getFrequency () { - return _istream.getRate(); + return _info.rate; } // documentation inherited public int read (ByteBuffer buf) throws IOException { - return _istream.read(buf, buf.position(), buf.remaining()); + ShortBuffer sbuf = buf.asShortBuffer(); + int channels = _info.channels; + int total = 0; + while (sbuf.hasRemaining()) { + int samples = Math.min(readSamples(), sbuf.remaining() / channels); + if (samples == 0) { + break; + } + int length = samples * channels; + if (_data.length < length) { + _data = new short[length]; + } + short[] data = _data; + for (int ii = 0; ii < channels; ii++) { + float[] pcm = _pcm[0][ii]; + int sidx = _offsets[ii], didx = ii; + for (int jj = 0; jj < samples; jj++) { + float value = Math.min(Math.max(pcm[sidx], -1f), +1f); + data[didx] = (short)(value * 32767f); + sidx++; + didx += channels; + } + } + sbuf.put(_data, 0, length); + _dsp.synthesis_read(samples); + total += (length * 2); + } + buf.position(buf.position() + total); + return total; } - /** The underlying Ogg input stream. */ - protected OggInputStream _istream; + /** + * Reads a buffer's worth of samples. + * + * @return the number of samples read, or zero if we've reached the end of the stream. + */ + protected int readSamples () + throws IOException + { + int samples; + while ((samples = _dsp.synthesis_pcmout(_pcm, _offsets)) <= 0) { + if (samples == 0 && !readPacket()) { + return 0; + } + if (_block.synthesis(_packet) == 0) { + _dsp.synthesis_blockin(_block); + } + } + return samples; + } + + /** + * Reads a packet. + * + * @return true if a packet was read, false if we've reached the end of the stream. + */ + protected boolean readPacket () + throws IOException + { + int result; + while ((result = _stream.packetout(_packet)) != 1) { + if (result == 0 && !readPage()) { + return false; + } + } + return true; + } + + /** + * Reads in a page. + * + * @return true if a page was read, false if we've reached the end of the stream. + */ + protected boolean readPage () + throws IOException + { + int result; + while ((result = _sync.pageout(_page)) != 1) { + if (result == 0 && !readChunk()) { + return false; + } + } + _stream.pagein(_page); + return true; + } + + /** + * Reads in a chunk of data from the underlying input stream. + * + * @return true if a chunk was read, false if we've reached the end of the stream. + */ + protected boolean readChunk () + throws IOException + { + int offset = _sync.buffer(BUFFER_SIZE); + int bytes = _in.read(_sync.data, offset, BUFFER_SIZE); + if (bytes > 0) { + _sync.wrote(bytes); + return true; + } + return false; + } + + /** The underlying input stream. */ + protected InputStream _in; + + /** The sync state. */ + protected SyncState _sync = new SyncState(); + + /** The stream state. */ + protected StreamState _stream = new StreamState(); + + /** The output page. */ + protected Page _page = new Page(); + + /** The output packet. */ + protected Packet _packet = new Packet(); + + /** The stream info. */ + protected Info _info = new Info(); + + /** The DSP state. */ + protected DspState _dsp = new DspState(); + + /** The output block. */ + protected Block _block = new Block(_dsp); + + /** Holds the decoded PCM data buffers. */ + protected float[][][] _pcm = new float[1][][]; + + /** The DSP offsets for each channel. */ + protected int[] _offsets; + + /** Intermediate storage for converted data. */ + protected short[] _data = new short[0]; + + /** The decode buffer size. */ + protected static final int BUFFER_SIZE = 4096 * 2; }