com.infinityk.simplesoundengine.OggInputStream Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jnuit-lwjgl-utils Show documentation
Show all versions of jnuit-lwjgl-utils Show documentation
jnuit lwjgl utility classes
/*
The MIT License (MIT)
Copyright (c) 2011 Asier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package com.infinityk.simplesoundengine;
import com.jcraft.jogg.Packet;
import com.jcraft.jogg.Page;
import com.jcraft.jogg.StreamState;
import com.jcraft.jogg.SyncState;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import org.lwjgl.BufferUtils;
import com.jcraft.jorbis.Block;
import com.jcraft.jorbis.Comment;
import com.jcraft.jorbis.DspState;
import com.jcraft.jorbis.Info;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* An input stream that can extract ogg data. This class is a bit of an
* experiment with continuations so uses thread where possibly not required.
* It's just a test to see if continuations make sense in some cases.
*
* @author Kevin Glass
*/
class OggInputStream extends InputStream {
private static final Logger logger = Logger.getLogger(OggInputStream.class.getName());
/**
* The conversion buffer size
*/
private int convsize = 4096 * 4;
/**
* The buffer used to read OGG file
*/
private byte[] convbuffer = new byte[convsize]; // take 8k out of the data segment, not the stack
/**
* The stream we're reading the OGG file from
*/
private InputStream input;
/**
* The audio information from the OGG header
*/
private Info oggInfo = new Info(); // struct that stores all the static vorbis bitstream settings
/**
* True if we're at the end of the available data
*/
private boolean endOfStream;
/**
* The Vorbis SyncState used to decode the OGG
*/
private SyncState syncState = new SyncState(); // sync and verify incoming physical bitstream
/**
* The Vorbis Stream State used to decode the OGG
*/
private StreamState streamState = new StreamState(); // take physical pages, weld into a logical stream of packets
/**
* The current OGG page
*/
private Page page = new Page(); // one Ogg bitstream page. Vorbis packets are inside
/**
* The current packet page
*/
private Packet packet = new Packet(); // one raw packet of data for decode
/**
* The comment read from the OGG file
*/
private Comment comment = new Comment(); // struct that stores all the bitstream user comments
/**
* The Vorbis DSP stat eused to decode the OGG
*/
private DspState dspState = new DspState(); // central working state for the packet->PCM decoder
/**
* The OGG block we're currently working with to convert PCM
*/
private Block vorbisBlock = new Block(dspState); // local working space for packet->PCM decode
/**
* Temporary scratch buffer
*/
byte[] buffer;
/**
* The number of bytes read
*/
int bytes = 0;
/**
* The true if we should be reading big endian
*/
boolean bigEndian = ByteOrder.nativeOrder().equals(ByteOrder.BIG_ENDIAN);
/**
* True if we're reached the end of the current bit stream
*/
boolean endOfBitStream = true;
/**
* True if we're initialise the OGG info block
*/
boolean inited = false;
/**
* The index into the byte array we currently read from
*/
private int readIndex;
/**
* The byte array store used to hold the data read from the ogg
*/
private ByteBuffer pcmBuffer = BufferUtils.createByteBuffer(4096 * 500);
/**
* The total number of bytes
*/
private int total;
private BufferedInputStream bis;
private FileInputStream fis;
/**
* Create a new stream to decode OGG data
*
* @param input The input stream from which to read the OGG file
* @throws IOException Indicates a failure to read from the supplied stream
*/
public OggInputStream(File f) throws IOException {
fis = new FileInputStream(f);
input = new BufferedInputStream(fis);
total = input.available();
init();
}
/**
* Create a new stream to decode OGG data
*
* @param input The input stream from which to read the OGG file
* @throws IOException Indicates a failure to read from the supplied stream
*/
public OggInputStream(InputStream input) throws IOException {
this.input = input;
total = input.available();
init();
}
/**
* Get the number of bytes on the stream
*
* @return The number of the bytes on the stream
*/
public int getLength() {
return total;
}
/**
* @see org.newdawn.slick.openal.AudioInputStream#getChannels()
*/
public int getChannels() {
return oggInfo.channels;
}
/**
* @see org.newdawn.slick.openal.AudioInputStream#getRate()
*/
public int getRate() {
return oggInfo.rate;
}
/**
* Initialise the streams and thread involved in the streaming of OGG data
*
* @throws IOException Indicates a failure to link up the streams
*/
private void init() throws IOException {
initVorbis();
readPCM();
}
/**
* @see java.io.InputStream#available()
*/
@Override
public int available() {
return endOfStream ? 0 : 1;
}
/**
* Initialise the vorbis decoding
*/
private void initVorbis() {
syncState.init();
}
/**
* Get a page and packet from that page
*
* @return True if there was a page available
*/
private boolean getPageAndPacket() {
// grab some data at the head of the stream. We want the first page
// (which is guaranteed to be small and only contain the Vorbis
// stream initial header) We need the first page to get the stream
// serialno.
// submit a 4k block to libvorbis' Ogg layer
int index = syncState.buffer(4096);
buffer = syncState.data;
if (buffer == null) {
endOfStream = true;
return false;
}
try {
bytes = input.read(buffer, index, 4096);
} catch (Exception e) {
logger.log(Level.WARNING, "Failure reading in vorbis", e);
endOfStream = true;
return false;
}
syncState.wrote(bytes);
// Get the first page.
if (syncState.pageout(page) != 1) {
// have we simply run out of data? If so, we're done.
if (bytes < 4096) {
return false;
}
// error case. Must not be Vorbis data
logger.log(Level.WARNING, "Input does not appear to be an Ogg bitstream.");
endOfStream = true;
return false;
}
// Get the serial number and set up the rest of decode.
// serialno first; use it to set up a logical stream
streamState.init(page.serialno());
// extract the initial header from the first page and verify that the
// Ogg bitstream is in fact Vorbis data
// I handle the initial header first instead of just having the code
// read all three Vorbis headers at once because reading the initial
// header is an easy way to identify a Vorbis bitstream and it's
// useful to see that functionality seperated out.
oggInfo.init();
comment.init();
if (streamState.pagein(page) < 0) {
// error; stream version mismatch perhaps
logger.log(Level.WARNING, "Error reading first page of Ogg bitstream data.");
endOfStream = true;
return false;
}
if (streamState.packetout(packet) != 1) {
// no page? must not be vorbis
logger.log(Level.WARNING, "Error reading initial header packet.");
endOfStream = true;
return false;
}
if (oggInfo.synthesis_headerin(comment, packet) < 0) {
// error case; not a vorbis header
logger.log(Level.WARNING, "This Ogg bitstream does not contain Vorbis audio data.");
endOfStream = true;
return false;
}
// At this point, we're sure we're Vorbis. We've set up the logical
// (Ogg) bitstream decoder. Get the comment and codebook headers and
// set up the Vorbis decoder
// The next two packets in order are the comment and codebook headers.
// They're likely large and may span multiple pages. Thus we reead
// and submit data until we get our two pacakets, watching that no
// pages are missing. If a page is missing, error out; losing a
// header page is the only place where missing data is fatal. */
int i = 0;
while (i < 2) {
while (i < 2) {
int result = syncState.pageout(page);
if (result == 0) {
break; // Need more data
} // Don't complain about missing or corrupt data yet. We'll
// catch it at the packet output phase
if (result == 1) {
streamState.pagein(page); // we can ignore any errors here
// as they'll also become apparent
// at packetout
while (i < 2) {
result = streamState.packetout(packet);
if (result == 0) {
break;
}
if (result == -1) {
// Uh oh; data at some point was corrupted or missing!
// We can't tolerate that in a header. Die.
logger.log(Level.WARNING, "Corrupt secondary header. Exiting.");
endOfStream = true;
return false;
}
oggInfo.synthesis_headerin(comment, packet);
i++;
}
}
}
// no harm in not checking before adding more
index = syncState.buffer(4096);
buffer = syncState.data;
try {
bytes = input.read(buffer, index, 4096);
} catch (Exception e) {
logger.log(Level.WARNING, "Failed to read Vorbis: ", e);
endOfStream = true;
return false;
}
if (bytes == 0 && i < 2) {
logger.log(Level.WARNING, "End of file before finding all Vorbis headers!");
endOfStream = true;
return false;
}
syncState.wrote(bytes);
}
convsize = 4096 / oggInfo.channels;
// OK, got and parsed all three headers. Initialize the Vorbis
// packet->PCM decoder.
dspState.synthesis_init(oggInfo); // central decode state
vorbisBlock.init(dspState); // local state for most of the decode
// so multiple block decodes can
// proceed in parallel. We could init
// multiple vorbis_block structures
// for vd here
return true;
}
/**
* Decode the OGG file as shown in the jogg/jorbis examples
*
* @throws IOException Indicates a failure to read from the supplied stream
*/
private void readPCM() throws IOException {
boolean wrote = false;
while (true) { // we repeat if the bitstream is chained
if (endOfBitStream) {
if (!getPageAndPacket()) {
break;
}
endOfBitStream = false;
}
if (!inited) {
inited = true;
return;
}
float[][][] _pcm = new float[1][][];
int[] _index = new int[oggInfo.channels];
// The rest is just a straight decode loop until end of stream
while (!endOfBitStream) {
while (!endOfBitStream) {
int result = syncState.pageout(page);
if (result == 0) {
break; // need more data
}
if (result == -1) { // missing or corrupt data at this page position
logger.log(Level.WARNING, "Corrupt or missing data in bitstream; continuing...");
} else {
streamState.pagein(page); // can safely ignore errors at
// this point
while (true) {
result = streamState.packetout(packet);
if (result == 0) {
break; // need more data
}
if (result == -1) { // missing or corrupt data at this page position
// no reason to complain; already complained above
} else {
// we have a packet. Decode it
int samples;
if (vorbisBlock.synthesis(packet) == 0) { // test for success!
dspState.synthesis_blockin(vorbisBlock);
}
// **pcm is a multichannel float vector. In stereo, for
// example, pcm[0] is left, and pcm[1] is right. samples is
// the size of each channel. Convert the float values
// (-1.<=range<=1.) to whatever PCM format and write it out
while ((samples = dspState.synthesis_pcmout(_pcm,
_index)) > 0) {
float[][] pcm = _pcm[0];
//boolean clipflag = false;
int bout = (samples < convsize ? samples
: convsize);
// convert floats to 16 bit signed ints (host order) and
// interleave
for (int i = 0; i < oggInfo.channels; i++) {
int ptr = i * 2;
//int ptr=i;
int mono = _index[i];
for (int j = 0; j < bout; j++) {
int val = (int) (pcm[i][mono + j] * 32767.);
// might as well guard against clipping
if (val > 32767) {
val = 32767;
}
if (val < -32768) {
val = -32768;
}
if (val < 0) {
val = val | 0x8000;
}
if (bigEndian) {
convbuffer[ptr] = (byte) (val >>> 8);
convbuffer[ptr + 1] = (byte) (val);
} else {
convbuffer[ptr] = (byte) (val);
convbuffer[ptr + 1] = (byte) (val >>> 8);
}
ptr += 2 * (oggInfo.channels);
}
}
int bytesToWrite = 2 * oggInfo.channels * bout;
if (bytesToWrite >= pcmBuffer.remaining()) {
logger.log(Level.WARNING, "Read block from OGG that was too big to be buffered: {0}", bytesToWrite);
} else {
pcmBuffer.put(convbuffer, 0, bytesToWrite);
}
wrote = true;
dspState.synthesis_read(bout); // tell libvorbis how
// many samples we
// actually consumed
}
}
}
if (page.eos() != 0) {
endOfBitStream = true;
}
if ((!endOfBitStream) && (wrote)) {
return;
}
}
}
if (!endOfBitStream) {
bytes = 0;
int index = syncState.buffer(4096);
if (index >= 0) {
buffer = syncState.data;
try {
bytes = input.read(buffer, index, 4096);
} catch (Exception e) {
logger.log(Level.WARNING, "Failure during vorbis decoding", e);
endOfStream = true;
return;
}
} else {
bytes = 0;
}
syncState.wrote(bytes);
if (bytes == 0) {
endOfBitStream = true;
}
}
}
// clean up this logical bitstream; before exit we see if we're
// followed by another [chained]
streamState.clear();
// ogg_page and ogg_packet structs always point to storage in
// libvorbis. They're never freed or manipulated directly
vorbisBlock.clear();
dspState.clear();
oggInfo.clear(); // must be called last
}
// OK, clean up the framer
syncState.clear();
endOfStream = true;
}
/**
* @see java.io.InputStream#read()
*/
@Override
public int read() throws IOException {
if (readIndex >= pcmBuffer.position()) {
pcmBuffer.clear();
readPCM();
readIndex = 0;
}
if (readIndex >= pcmBuffer.position()) {
return -1;
}
int value = pcmBuffer.get(readIndex);
if (value < 0) {
value = 256 + value;
}
readIndex++;
return value;
}
/**
* @see org.newdawn.slick.openal.AudioInputStream#atEnd()
*/
public boolean atEnd() {
return endOfStream && (readIndex >= pcmBuffer.position());
}
/**
* @see java.io.InputStream#read(byte[], int, int)
*/
@Override
public int read(byte[] b, int off, int len) throws IOException {
for (int i = 0; i < len; i++) {
try {
int value = read();
if (value >= 0) {
b[i] = (byte) value;
} else {
if (i == 0) {
return -1;
} else {
return i;
}
}
} catch (IOException e) {
logger.log(Level.WARNING, "error reading ogg stream", e);
return i;
}
}
return len;
}
/**
* @see java.io.InputStream#read(byte[])
*/
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
/**
* @see java.io.InputStream#close()
*/
@Override
public void close() throws IOException {
input.close();
}
}