Path: blob/master/SLICK_HOME/src/org/newdawn/slick/openal/OggInputStream.java
1461 views
package org.newdawn.slick.openal;12import java.io.IOException;3import java.io.InputStream;4import java.nio.ByteBuffer;5import java.nio.ByteOrder;67import org.lwjgl.BufferUtils;8import org.newdawn.slick.util.Log;910import com.jcraft.jogg.Packet;11import com.jcraft.jogg.Page;12import com.jcraft.jogg.StreamState;13import com.jcraft.jogg.SyncState;14import com.jcraft.jorbis.Block;15import com.jcraft.jorbis.Comment;16import com.jcraft.jorbis.DspState;17import com.jcraft.jorbis.Info;1819/**20* An input stream that can extract ogg data. This class is a bit of an experiment with continuations21* so uses thread where possibly not required. It's just a test to see if continuations make sense in22* some cases.23*24* @author kevin25*/26public class OggInputStream extends InputStream implements AudioInputStream {27/** The conversion buffer size */28private int convsize = 4096 * 4;29/** The buffer used to read OGG file */30private byte[] convbuffer = new byte[convsize]; // take 8k out of the data segment, not the stack31/** The stream we're reading the OGG file from */32private InputStream input;33/** The audio information from the OGG header */34private Info oggInfo = new Info(); // struct that stores all the static vorbis bitstream settings35/** True if we're at the end of the available data */36private boolean endOfStream;3738/** The Vorbis SyncState used to decode the OGG */39private SyncState syncState = new SyncState(); // sync and verify incoming physical bitstream40/** The Vorbis Stream State used to decode the OGG */41private StreamState streamState = new StreamState(); // take physical pages, weld into a logical stream of packets42/** The current OGG page */43private Page page = new Page(); // one Ogg bitstream page. Vorbis packets are inside44/** The current packet page */45private Packet packet = new Packet(); // one raw packet of data for decode4647/** The comment read from the OGG file */48private Comment comment = new Comment(); // struct that stores all the bitstream user comments49/** The Vorbis DSP stat eused to decode the OGG */50private DspState dspState = new DspState(); // central working state for the packet->PCM decoder51/** The OGG block we're currently working with to convert PCM */52private Block vorbisBlock = new Block(dspState); // local working space for packet->PCM decode5354/** Temporary scratch buffer */55byte[] buffer;56/** The number of bytes read */57int bytes = 0;58/** The true if we should be reading big endian */59boolean bigEndian = ByteOrder.nativeOrder().equals(ByteOrder.BIG_ENDIAN);60/** True if we're reached the end of the current bit stream */61boolean endOfBitStream = true;62/** True if we're initialise the OGG info block */63boolean inited = false;6465/** The index into the byte array we currently read from */66private int readIndex;67/** The byte array store used to hold the data read from the ogg */68private ByteBuffer pcmBuffer = BufferUtils.createByteBuffer(4096 * 500);69/** The total number of bytes */70private int total;7172/**73* Create a new stream to decode OGG data74*75* @param input The input stream from which to read the OGG file76* @throws IOException Indicates a failure to read from the supplied stream77*/78public OggInputStream(InputStream input) throws IOException {79this.input = input;80total = input.available();8182init();83}8485/**86* Get the number of bytes on the stream87*88* @return The number of the bytes on the stream89*/90public int getLength() {91return total;92}9394/**95* @see org.newdawn.slick.openal.AudioInputStream#getChannels()96*/97public int getChannels() {98return oggInfo.channels;99}100101/**102* @see org.newdawn.slick.openal.AudioInputStream#getRate()103*/104public int getRate() {105return oggInfo.rate;106}107108/**109* Initialise the streams and thread involved in the streaming of OGG data110*111* @throws IOException Indicates a failure to link up the streams112*/113private void init() throws IOException {114initVorbis();115readPCM();116}117118/**119* @see java.io.InputStream#available()120*/121public int available() {122return endOfStream ? 0 : 1;123}124125/**126* Initialise the vorbis decoding127*/128private void initVorbis() {129syncState.init();130}131132/**133* Get a page and packet from that page134*135* @return True if there was a page available136*/137private boolean getPageAndPacket() {138// grab some data at the head of the stream. We want the first page139// (which is guaranteed to be small and only contain the Vorbis140// stream initial header) We need the first page to get the stream141// serialno.142143// submit a 4k block to libvorbis' Ogg layer144int index = syncState.buffer(4096);145146buffer = syncState.data;147if (buffer == null) {148endOfStream = true;149return false;150}151152try {153bytes = input.read(buffer, index, 4096);154} catch (Exception e) {155Log.error("Failure reading in vorbis");156Log.error(e);157endOfStream = true;158return false;159}160syncState.wrote(bytes);161162// Get the first page.163if (syncState.pageout(page) != 1) {164// have we simply run out of data? If so, we're done.165if (bytes < 4096)166return false;167168// error case. Must not be Vorbis data169Log.error("Input does not appear to be an Ogg bitstream.");170endOfStream = true;171return false;172}173174// Get the serial number and set up the rest of decode.175// serialno first; use it to set up a logical stream176streamState.init(page.serialno());177178// extract the initial header from the first page and verify that the179// Ogg bitstream is in fact Vorbis data180181// I handle the initial header first instead of just having the code182// read all three Vorbis headers at once because reading the initial183// header is an easy way to identify a Vorbis bitstream and it's184// useful to see that functionality seperated out.185186oggInfo.init();187comment.init();188if (streamState.pagein(page) < 0) {189// error; stream version mismatch perhaps190Log.error("Error reading first page of Ogg bitstream data.");191endOfStream = true;192return false;193}194195if (streamState.packetout(packet) != 1) {196// no page? must not be vorbis197Log.error("Error reading initial header packet.");198endOfStream = true;199return false;200}201202if (oggInfo.synthesis_headerin(comment, packet) < 0) {203// error case; not a vorbis header204Log.error("This Ogg bitstream does not contain Vorbis audio data.");205endOfStream = true;206return false;207}208209// At this point, we're sure we're Vorbis. We've set up the logical210// (Ogg) bitstream decoder. Get the comment and codebook headers and211// set up the Vorbis decoder212213// The next two packets in order are the comment and codebook headers.214// They're likely large and may span multiple pages. Thus we reead215// and submit data until we get our two pacakets, watching that no216// pages are missing. If a page is missing, error out; losing a217// header page is the only place where missing data is fatal. */218219int i = 0;220while (i < 2) {221while (i < 2) {222223int result = syncState.pageout(page);224if (result == 0)225break; // Need more data226// Don't complain about missing or corrupt data yet. We'll227// catch it at the packet output phase228229if (result == 1) {230streamState.pagein(page); // we can ignore any errors here231// as they'll also become apparent232// at packetout233while (i < 2) {234result = streamState.packetout(packet);235if (result == 0)236break;237if (result == -1) {238// Uh oh; data at some point was corrupted or missing!239// We can't tolerate that in a header. Die.240Log.error("Corrupt secondary header. Exiting.");241endOfStream = true;242return false;243}244245oggInfo.synthesis_headerin(comment, packet);246i++;247}248}249}250// no harm in not checking before adding more251index = syncState.buffer(4096);252buffer = syncState.data;253try {254bytes = input.read(buffer, index, 4096);255} catch (Exception e) {256Log.error("Failed to read Vorbis: ");257Log.error(e);258endOfStream = true;259return false;260}261if (bytes == 0 && i < 2) {262Log.error("End of file before finding all Vorbis headers!");263endOfStream = true;264return false;265}266syncState.wrote(bytes);267}268269convsize = 4096 / oggInfo.channels;270271// OK, got and parsed all three headers. Initialize the Vorbis272// packet->PCM decoder.273dspState.synthesis_init(oggInfo); // central decode state274vorbisBlock.init(dspState); // local state for most of the decode275// so multiple block decodes can276// proceed in parallel. We could init277// multiple vorbis_block structures278// for vd here279280return true;281}282283/**284* Decode the OGG file as shown in the jogg/jorbis examples285*286* @throws IOException Indicates a failure to read from the supplied stream287*/288private void readPCM() throws IOException {289boolean wrote = false;290291while (true) { // we repeat if the bitstream is chained292if (endOfBitStream) {293if (!getPageAndPacket()) {294break;295}296endOfBitStream = false;297}298299if (!inited) {300inited = true;301return;302}303304float[][][] _pcm = new float[1][][];305int[] _index = new int[oggInfo.channels];306// The rest is just a straight decode loop until end of stream307while (!endOfBitStream) {308while (!endOfBitStream) {309int result = syncState.pageout(page);310311if (result == 0) {312break; // need more data313}314315if (result == -1) { // missing or corrupt data at this page position316Log.error("Corrupt or missing data in bitstream; continuing...");317} else {318streamState.pagein(page); // can safely ignore errors at319// this point320while (true) {321result = streamState.packetout(packet);322323if (result == 0)324break; // need more data325if (result == -1) { // missing or corrupt data at this page position326// no reason to complain; already complained above327} else {328// we have a packet. Decode it329int samples;330if (vorbisBlock.synthesis(packet) == 0) { // test for success!331dspState.synthesis_blockin(vorbisBlock);332}333334// **pcm is a multichannel float vector. In stereo, for335// example, pcm[0] is left, and pcm[1] is right. samples is336// the size of each channel. Convert the float values337// (-1.<=range<=1.) to whatever PCM format and write it out338339while ((samples = dspState.synthesis_pcmout(_pcm,340_index)) > 0) {341float[][] pcm = _pcm[0];342//boolean clipflag = false;343int bout = (samples < convsize ? samples344: convsize);345346// convert floats to 16 bit signed ints (host order) and347// interleave348for (int i = 0; i < oggInfo.channels; i++) {349int ptr = i * 2;350//int ptr=i;351int mono = _index[i];352for (int j = 0; j < bout; j++) {353int val = (int) (pcm[i][mono + j] * 32767.);354// might as well guard against clipping355if (val > 32767) {356val = 32767;357}358if (val < -32768) {359val = -32768;360}361if (val < 0)362val = val | 0x8000;363364if (bigEndian) {365convbuffer[ptr] = (byte) (val >>> 8);366convbuffer[ptr + 1] = (byte) (val);367} else {368convbuffer[ptr] = (byte) (val);369convbuffer[ptr + 1] = (byte) (val >>> 8);370}371ptr += 2 * (oggInfo.channels);372}373}374375int bytesToWrite = 2 * oggInfo.channels * bout;376if (bytesToWrite >= pcmBuffer.remaining()) {377Log.warn("Read block from OGG that was too big to be buffered: " + bytesToWrite);378} else {379pcmBuffer.put(convbuffer, 0, bytesToWrite);380}381382wrote = true;383dspState.synthesis_read(bout); // tell libvorbis how384// many samples we385// actually consumed386}387}388}389if (page.eos() != 0) {390endOfBitStream = true;391}392393if ((!endOfBitStream) && (wrote)) {394return;395}396}397}398399if (!endOfBitStream) {400bytes = 0;401int index = syncState.buffer(4096);402if (index >= 0) {403buffer = syncState.data;404try {405bytes = input.read(buffer, index, 4096);406} catch (Exception e) {407Log.error("Failure during vorbis decoding");408Log.error(e);409endOfStream = true;410return;411}412} else {413bytes = 0;414}415syncState.wrote(bytes);416if (bytes == 0) {417endOfBitStream = true;418}419}420}421422// clean up this logical bitstream; before exit we see if we're423// followed by another [chained]424streamState.clear();425426// ogg_page and ogg_packet structs always point to storage in427// libvorbis. They're never freed or manipulated directly428429vorbisBlock.clear();430dspState.clear();431oggInfo.clear(); // must be called last432}433434// OK, clean up the framer435syncState.clear();436endOfStream = true;437}438439/**440* @see java.io.InputStream#read()441*/442public int read() throws IOException {443if (readIndex >= pcmBuffer.position()) {444pcmBuffer.clear();445readPCM();446readIndex = 0;447}448if (readIndex >= pcmBuffer.position()) {449return -1;450}451452int value = pcmBuffer.get(readIndex);453if (value < 0) {454value = 256 + value;455}456readIndex++;457458return value;459}460461/**462* @see org.newdawn.slick.openal.AudioInputStream#atEnd()463*/464public boolean atEnd() {465return endOfStream && (readIndex >= pcmBuffer.position());466}467468/**469* @see java.io.InputStream#read(byte[], int, int)470*/471public int read(byte[] b, int off, int len) throws IOException {472for (int i=0;i<len;i++) {473try {474int value = read();475if (value >= 0) {476b[i] = (byte) value;477} else {478if (i == 0) {479return -1;480} else {481return i;482}483}484} catch (IOException e) {485Log.error(e);486return i;487}488}489490return len;491}492493/**494* @see java.io.InputStream#read(byte[])495*/496public int read(byte[] b) throws IOException {497return read(b, 0, b.length);498}499500/**501* @see java.io.InputStream#close()502*/503public void close() throws IOException {504}505}506507508