1949014c76
git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@611 ed5b42cb-e716-0410-a449-f6a68f950b19
242 lines
6.9 KiB
Java
242 lines
6.9 KiB
Java
//
|
|
// $Id$
|
|
//
|
|
// Nenya library - tools for developing networked games
|
|
// Copyright (C) 2002-2007 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 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
|
|
{
|
|
@Override
|
|
public void init (InputStream in)
|
|
throws IOException
|
|
{
|
|
_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];
|
|
}
|
|
|
|
@Override
|
|
public int getFormat ()
|
|
{
|
|
return (_info.channels == 1) ? AL10.AL_FORMAT_MONO16 : AL10.AL_FORMAT_STEREO16;
|
|
}
|
|
|
|
@Override
|
|
public int getFrequency ()
|
|
{
|
|
return _info.rate;
|
|
}
|
|
|
|
@Override
|
|
public int read (ByteBuffer buf)
|
|
throws IOException
|
|
{
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|