
com.threerings.openal.OggStreamDecoder Maven / Gradle / Ivy
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// https://github.com/threerings/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;
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy