Skip to content

Instantly share code, notes, and snippets.

@windy1
Created June 1, 2013 21:52
Show Gist options
  • Save windy1/5691827 to your computer and use it in GitHub Desktop.
Save windy1/5691827 to your computer and use it in GitHub Desktop.
/*
* This file is part of Spout.
*
* Copyright (c) 2011-2012, Spout LLC <http://www.spout.org/>
* Spout is licensed under the Spout License Version 1.
*
* Spout 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 3 of the License, or (at your option)
* any later version.
*
* In addition, 180 days after any changes are published, you can use the
* software, incorporating those changes, under the terms of the MIT license,
* as described in the Spout License Version 1.
*
* Spout 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,
* the MIT license and the Spout License Version 1 along with this program.
* If not, see <http://www.gnu.org/licenses/> for the GNU Lesser General Public
* License and see <http://spout.in/licensev1> for the full license, including
* the MIT license.
*/
package org.spout.engine.filesystem.resource.loader.audio.ogg;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
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;
import org.lwjgl.BufferUtils;
import static org.lwjgl.openal.AL10.*;
public class OggData {
private final InputStream in;
// data needed for OpenAL
public final ByteBuffer data = BufferUtils.createByteBuffer(BUFFER_SIZE * 500);
public int channels;
public int format;
public int samplerate;
// raw data
private static final int BUFFER_SIZE = 4096;
private byte[] buffer;
private int count = 0;
private int index = 0;
// conversion data (used by JOrbis decoding)
private int convertedBufferSize = BUFFER_SIZE * 4;
private byte[] convertedBuffer = new byte[convertedBufferSize];
// pcm data
private float[][][] pcmData;
private int[] pcmIndex;
// jogg
private final Packet packet = new Packet();
private final Page page = new Page();
private final StreamState streamState = new StreamState();
private final SyncState syncState = new SyncState();
// jorbis
private final DspState dspState = new DspState();
private final Block block = new Block(dspState);
private final Comment comment = new Comment();
private final Info info = new Info();
private OggData(InputStream in) {
this.in = in;
}
public static OggData create(InputStream in) {
OggData data = new OggData(in);
data.init();
System.out.println("remaining: " + data.data.remaining());
try {
data.readHeader();
} catch (IOException e) {
throw new IllegalStateException("Error reading Ogg header: ", e);
}
try {
data.readBody();
} catch (IOException e) {
throw new IllegalStateException("Error reading Ogg body: ", e);
}
return data;
}
public void dispose() {
streamState.clear();
block.clear();
dspState.clear();
info.clear();
syncState.clear();
}
private void init() {
syncState.init();
syncState.buffer(BUFFER_SIZE);
buffer = syncState.data;
}
private void readHeader() throws IOException {
int packet = 1;
boolean completed = false;
while (!completed) {
// read input data and notify the sync state of how many bytes we read
count = in.read(buffer, index, BUFFER_SIZE);
syncState.wrote(count);
switch (packet) {
// first packet
case 1:
switch (syncState.pageout(page)) {
case -1:
throw new IOException("Hole found in first packet data.");
case 0:
// needs more data, break.
break;
// succsessfully read the first packet
// initialize info and command for next run
case 1:
// reset the state
streamState.init(page.serialno());
streamState.reset();
// reset info and comment
info.init();
comment.init();
// make sure everything is in order
if (streamState.pagein(page) == -1
|| streamState.packetout(this.packet) != 1
|| info.synthesis_headerin(comment, this.packet) < 0) {
throw new IOException("Error while reading header.");
}
// done, increment the packet for next run
packet++;
break;
}
// packet still equals 1, we're done here
if (packet == 1) {
break;
}
case 2:
case 3:
switch (syncState.pageout(page)) {
case -1:
throw new IOException("Hole found in second or third packet data.");
case 0:
// need more data
break;
case 1:
// extract the page data
streamState.pagein(page);
switch (streamState.packetout(this.packet)) {
case -1:
throw new IOException("Hole found in first packet data.");
case 0:
break;
// found a packet, process it
case 1:
info.synthesis_headerin(comment, this.packet);
if (++packet == 4) {
// the third packet is the last
completed = true;
}
break;
}
break;
}
break;
}
// get the updated index and data
index = syncState.buffer(BUFFER_SIZE);
buffer = syncState.data;
if (count == 0 && !completed) {
throw new IOException("Not enough header data available.");
}
}
}
private void readBody() throws IOException {
dspState.synthesis_init(info);
block.init(dspState);
channels = info.channels;
samplerate = info.rate;
format = channels > 1 ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16;
convertedBufferSize = BUFFER_SIZE / channels;
pcmData = new float[1][][];
pcmIndex = new int[channels];
// read the body
boolean completed = false;
while (!completed) {
switch (syncState.pageout(page)) {
case 0:
break;
case 1:
streamState.pagein(page);
if (page.granulepos() == 0) {
completed = true;
break;
}
// start processing packets
processPackets : while (true) {
switch (streamState.packetout(packet)) {
case 0:
break processPackets;
case 1:
// decode packet
decodeCurrentPacket();
}
}
if (page.eos() != 0) {
completed = true;
}
}
if (!completed) {
this.index = syncState.buffer(BUFFER_SIZE);
buffer = syncState.data;
count = in.read(buffer, this.index, BUFFER_SIZE);
syncState.wrote(count);
if (count == 0) {
completed = true;
}
}
}
}
private void decodeCurrentPacket() {
int samples;
if (block.synthesis(packet) == 0) {
dspState.synthesis_blockin(block);
}
int range;
while ((samples = dspState.synthesis_pcmout(pcmData, pcmIndex)) > 0) {
if (samples < convertedBufferSize) {
range = samples;
} else {
range = convertedBufferSize;
}
// for each channel
for (int i = 0; i < channels; i++) {
int sampleIndex = i * 2;
for (int j = 0; j < range; j++) {
// get the current value at the current channel and sample
int value = Math.max(Math.min(32767, (int) (pcmData[0][i][pcmIndex[i] + j] * 32767)), -32768);
if (value < 0) {
value = value | 32768;
}
convertedBuffer[sampleIndex] = (byte) value;
convertedBuffer[sampleIndex + 1] = (byte) (value >>> 8);
sampleIndex += 2 * channels;
}
}
int toWrite = channels * range * 2;
if (data.remaining() < toWrite) {
throw new IllegalStateException("Block is to big to write to buffer: " + toWrite);
}
data.put(convertedBuffer, 0, toWrite);
dspState.synthesis_read(toWrite);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment