Package org.vorbis.spi.sampled.convert

Source Code of org.vorbis.spi.sampled.convert.DecodedVorbisAudioInputStream

/*
* Copyright (C) 2008 JavaZOOM
*               2013 Trilarion
*
* This program 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.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package org.vorbis.spi.sampled.convert;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import org.sound.TDebug;
import org.sound.sampled.convert.TAsynchronousFilteredAudioInputStream;
import org.vorbis.jcraft.jogg.Packet;
import org.vorbis.jcraft.jogg.Page;
import org.vorbis.jcraft.jogg.StreamState;
import org.vorbis.jcraft.jogg.SyncState;
import org.vorbis.jcraft.jorbis.Block;
import org.vorbis.jcraft.jorbis.Comment;
import org.vorbis.jcraft.jorbis.DspState;
import org.vorbis.jcraft.jorbis.Info;
import org.vorbis.spi.PropertiesContainer;

/**
* This class implements the Vorbis decoding.
*/
public class DecodedVorbisAudioInputStream extends TAsynchronousFilteredAudioInputStream implements PropertiesContainer {

    private InputStream oggBitStream_ = null;
    private SyncState oggSyncState_ = null;
    private StreamState oggStreamState_ = null;
    private Page oggPage_ = null;
    private Packet oggPacket_ = null;
    private Info vorbisInfo = null;
    private Comment vorbisComment = null;
    private DspState vorbisDspState = null;
    private Block vorbisBlock = null;
    static final int playState_NeedHeaders = 0;
    static final int playState_ReadData = 1;
    static final int playState_WriteData = 2;
    static final int playState_Done = 3;
    static final int playState_BufferFull = 4;
    static final int playState_Corrupt = -1;
    private int playState;
    private int bufferMultiple_ = 4;
    private int bufferSize_ = bufferMultiple_ * 256 * 2;
    private int convsize = bufferSize_ * 2;
    private byte[] convbuffer = new byte[convsize];
    private byte[] buffer = null;
    private int bytes = 0;
    private float[][][] _pcmf = null;
    private int[] _index = null;
    private int index = 0;
    private int i = 0;
    // bout is now a global so that we can continue from when we have a buffer full.
    int bout = 0;
    private HashMap properties = null;
    private long currentBytes = 0;

    /**
     * Constructor.
     */
    public DecodedVorbisAudioInputStream(AudioFormat outputFormat, AudioInputStream bitStream) {
        super(outputFormat, -1);
        this.oggBitStream_ = bitStream;
        init_jorbis();
        index = 0;
        playState = playState_NeedHeaders;
        properties = new HashMap();
    }

    /**
     * Initializes all the jOrbis and jOgg vars that are used for song playback.
     */
    private void init_jorbis() {
        oggSyncState_ = new SyncState();
        oggStreamState_ = new StreamState();
        oggPage_ = new Page();
        oggPacket_ = new Packet();
        vorbisInfo = new Info();
        vorbisComment = new Comment();
        vorbisDspState = new DspState();
        vorbisBlock = new Block(vorbisDspState);
        buffer = null;
        bytes = 0;
        currentBytes = 0L;
        oggSyncState_.init();
    }

    /**
     * Return dynamic properties.
     *
     * <ul> <li><b>ogg.position.byte</b> [Long], current position in bytes in
     * the stream. </ul>
     */
    @Override
    public Map properties() {
        properties.put("ogg.position.byte", currentBytes);
        return properties;
    }

    /**
     * Main loop.
     */
    @Override
    public void execute() {
        if (TDebug.TraceAudioConverter) {
            switch (playState) {
                case playState_NeedHeaders:
                    TDebug.out("playState = playState_NeedHeaders");
                    break;
                case playState_ReadData:
                    TDebug.out("playState = playState_ReadData");
                    break;
                case playState_WriteData:
                    TDebug.out("playState = playState_WriteData");
                    break;
                case playState_Done:
                    TDebug.out("playState = playState_Done");
                    break;
                case playState_BufferFull:
                    TDebug.out("playState = playState_BufferFull");
                    break;
                case playState_Corrupt:
                    TDebug.out("playState = playState_Corrupt");
                    break;
            }
        }
        // This code was developed by the jCraft group, as JOrbisPlayer.java,  slightly
        // modified by jOggPlayer developer and adapted by JavaZOOM to suit the JavaSound
        // SPI. Then further modified by Tom Kimpton to correctly play ogg files that
        // would hang the player.
        switch (playState) {
            case playState_NeedHeaders:
                try {
                    // Headers (+ Comments).
                    readHeaders();
                } catch (IOException ioe) {
                    playState = playState_Corrupt;
                    return;
                }
                playState = playState_ReadData;
                break;

            case playState_ReadData:
                int result;
                index = oggSyncState_.buffer(bufferSize_);
                buffer = oggSyncState_.data;
                bytes = readFromStream(buffer, index, bufferSize_);
                if (TDebug.TraceAudioConverter) {
                    TDebug.out("More data : " + bytes);
                }
                if (bytes == -1) {
                    playState = playState_Done;
                    if (TDebug.TraceAudioConverter) {
                        TDebug.out("Ogg Stream empty. Settings playState to playState_Done.");
                    }
                    break;
                } else {
                    oggSyncState_.wrote(bytes);
                    if (bytes == 0) {
                        if ((oggPage_.eos() != 0) || (oggStreamState_.e_o_s != 0) || (oggPacket_.e_o_s != 0)) {
                            if (TDebug.TraceAudioConverter) {
                                TDebug.out("oggSyncState wrote 0 bytes: settings playState to playState_Done.");
                            }
                            playState = playState_Done;
                        }
                        if (TDebug.TraceAudioConverter) {
                            TDebug.out("oggSyncState wrote 0 bytes: but stream not yet empty.");
                        }
                        break;
                    }
                }

                result = oggSyncState_.pageout(oggPage_);
                if (result == 0) {
                    if (TDebug.TraceAudioConverter) {
                        TDebug.out("Setting playState to playState_ReadData.");
                    }
                    playState = playState_ReadData;
                    break;
                } // need more data
                if (result == -1) { // missing or corrupt data at this page position
                    if (TDebug.TraceAudioConverter) {
                        TDebug.out("Corrupt or missing data in bitstream; setting playState to playState_ReadData");
                    }
                    playState = playState_ReadData;
                    break;
                }

                oggStreamState_.pagein(oggPage_);

                if (TDebug.TraceAudioConverter) {
                    TDebug.out("Setting playState to playState_WriteData.");
                }
                playState = playState_WriteData;
                break;

            case playState_WriteData:
                // Decoding !
                if (TDebug.TraceAudioConverter) {
                    TDebug.out("Decoding");
                }
                while (true) {
                    result = oggStreamState_.packetout(oggPacket_);
                    if (result == 0) {
                        if (TDebug.TraceAudioConverter) {
                            TDebug.out("Packetout returned 0, going to read state.");
                        }
                        playState = playState_ReadData;
                        break;
                    } // need more data
                    else if (result == -1) {
                        // missing or corrupt data at this page position
                        // no reason to complain; already complained above
                        if (TDebug.TraceAudioConverter) {
                            TDebug.out("Corrupt or missing data in packetout bitstream; going to read state...");
                        }
                        // playState = playState_ReadData;
                        // break;
                        continue;
                    } else {
                        // we have a packet.  Decode it
                        if (vorbisBlock.synthesis(oggPacket_) == 0) { // test for success!
                            vorbisDspState.synthesis_blockin(vorbisBlock);
                        } else {
                            //if(TDebug.TraceAudioConverter) TDebug.out("vorbisBlock.synthesis() returned !0, going to read state");
                            if (TDebug.TraceAudioConverter) {
                                TDebug.out("VorbisBlock.synthesis() returned !0, continuing.");
                            }
                            continue;
                        }

                        outputSamples();
                        if (playState == playState_BufferFull) {
                            return;
                        }

                    } // else result != -1
                } // while(true)
                if (oggPage_.eos() != 0) {
                    if (TDebug.TraceAudioConverter) {
                        TDebug.out("Settings playState to playState_Done.");
                    }
                    playState = playState_Done;
                }
                break;
            case playState_BufferFull:
                continueFromBufferFull();
                break;

            case playState_Corrupt:
                if (TDebug.TraceAudioConverter) {
                    TDebug.out("Corrupt Song.");
                }
            // drop through to playState_Done...
            case playState_Done:
                oggStreamState_.clear();
                vorbisBlock.clear();
                vorbisDspState.clear();
                vorbisInfo.clear();
                oggSyncState_.clear();
                if (TDebug.TraceAudioConverter) {
                    TDebug.out("Done Song.");
                }
                try {
                    if (oggBitStream_ != null) {
                        oggBitStream_.close();
                    }
                    getCircularBuffer().close();
                } catch (Exception e) {
                    if (TDebug.TraceAudioConverter) {
                        TDebug.out(e.getMessage());
                    }
                }
                break;
        } // switch
    }

    /**
     * This routine was extracted so that when the output buffer fills up, we
     * can break out of the loop, let the music channel drain, then continue
     * from where we were.
     */
    private void outputSamples() {
        int samples;
        while ((samples = vorbisDspState.synthesis_pcmout(_pcmf, _index)) > 0) {
            float[][] pcmf = _pcmf[0];
            bout = (samples < convsize ? samples : convsize);
            double fVal;
            // convert doubles to 16 bit signed ints (host order) and
            // interleave
            for (i = 0; i < vorbisInfo.channels; i++) {
                int pointer = i * 2;
                //int ptr=i;
                int mono = _index[i];
                for (int j = 0; j < bout; j++) {
                    fVal = pcmf[i][mono + j] * 32767.;
                    int val = (int) (fVal);
                    if (val > 32767) {
                        val = 32767;
                    }
                    if (val < -32768) {
                        val = -32768;
                    }
                    if (val < 0) {
                        val |= 0x8000;
                    }
                    convbuffer[pointer] = (byte) (val);
                    convbuffer[pointer + 1] = (byte) (val >>> 8);
                    pointer += 2 * (vorbisInfo.channels);
                }
            }
            if (TDebug.TraceAudioConverter) {
                TDebug.out("about to write: " + 2 * vorbisInfo.channels * bout);
            }
            if (getCircularBuffer().availableWrite() < 2 * vorbisInfo.channels * bout) {
                if (TDebug.TraceAudioConverter) {
                    TDebug.out("Too much data in this data packet, better return, let the channel drain, and try again...");
                }
                playState = playState_BufferFull;
                return;
            }
            getCircularBuffer().write(convbuffer, 0, 2 * vorbisInfo.channels * bout);
            if (bytes < bufferSize_) {
                if (TDebug.TraceAudioConverter) {
                    TDebug.out("Finished with final buffer of music?");
                }
            }
            if (vorbisDspState.synthesis_read(bout) != 0) {
                if (TDebug.TraceAudioConverter) {
                    TDebug.out("VorbisDspState.synthesis_read returned -1.");
                }
            }
        } // while(samples...)
        playState = playState_ReadData;
    }

    private void continueFromBufferFull() {
        if (getCircularBuffer().availableWrite() < 2 * vorbisInfo.channels * bout) {
            if (TDebug.TraceAudioConverter) {
                TDebug.out("Too much data in this data packet, better return, let the channel drain, and try again...");
            }
            // Don't change play state.
            return;
        }
        getCircularBuffer().write(convbuffer, 0, 2 * vorbisInfo.channels * bout);
        // Don't change play state. Let outputSamples change play state, if necessary.
        outputSamples();
    }

    /**
     * Reads headers and comments.
     */
    private void readHeaders() throws IOException {
        if (TDebug.TraceAudioConverter) {
            TDebug.out("readHeaders(");
        }
        index = oggSyncState_.buffer(bufferSize_);
        buffer = oggSyncState_.data;
        bytes = readFromStream(buffer, index, bufferSize_);
        if (bytes == -1) {
            if (TDebug.TraceAudioConverter) {
                TDebug.out("Cannot get any data from selected Ogg bitstream.");
            }
            throw new IOException("Cannot get any data from selected Ogg bitstream.");
        }
        oggSyncState_.wrote(bytes);
        if (oggSyncState_.pageout(oggPage_) != 1) {
            if (bytes < bufferSize_) {
                throw new IOException("EOF");
            }
            if (TDebug.TraceAudioConverter) {
                TDebug.out("Input does not appear to be an Ogg bitstream.");
            }
            throw new IOException("Input does not appear to be an Ogg bitstream.");
        }
        oggStreamState_.init(oggPage_.serialno());
        vorbisInfo.init();
        vorbisComment.init();
        if (oggStreamState_.pagein(oggPage_) < 0) {
            // error; stream version mismatch perhaps
            if (TDebug.TraceAudioConverter) {
                TDebug.out("Error reading first page of Ogg bitstream data.");
            }
            throw new IOException("Error reading first page of Ogg bitstream data.");
        }
        if (oggStreamState_.packetout(oggPacket_) != 1) {
            // no page? must not be vorbis
            if (TDebug.TraceAudioConverter) {
                TDebug.out("Error reading initial header packet.");
            }
            throw new IOException("Error reading initial header packet.");
        }
        if (vorbisInfo.synthesis_headerin(vorbisComment, oggPacket_) < 0) {
            // error case; not a vorbis header
            if (TDebug.TraceAudioConverter) {
                TDebug.out("This Ogg bitstream does not contain Vorbis audio data.");
            }
            throw new IOException("This Ogg bitstream does not contain Vorbis audio data.");
        }
        //int i = 0;
        i = 0;
        while (i < 2) {
            while (i < 2) {
                int result = oggSyncState_.pageout(oggPage_);
                if (result == 0) {
                    break;
                } // Need more data
                if (result == 1) {
                    oggStreamState_.pagein(oggPage_);
                    while (i < 2) {
                        result = oggStreamState_.packetout(oggPacket_);
                        if (result == 0) {
                            break;
                        }
                        if (result == -1) {
                            if (TDebug.TraceAudioConverter) {
                                TDebug.out("Corrupt secondary header.  Exiting.");
                            }
                            throw new IOException("Corrupt secondary header.  Exiting.");
                        }
                        vorbisInfo.synthesis_headerin(vorbisComment, oggPacket_);
                        i++;
                    }
                }
            }
            index = oggSyncState_.buffer(bufferSize_);
            buffer = oggSyncState_.data;
            bytes = readFromStream(buffer, index, bufferSize_);
            if (bytes == -1) {
                break;
            }
            if (bytes == 0 && i < 2) {
                if (TDebug.TraceAudioConverter) {
                    TDebug.out("End of file before finding all Vorbis headers!");
                }
                throw new IOException("End of file before finding all Vorbis  headers!");
            }
            oggSyncState_.wrote(bytes);
        }

        byte[][] ptr = vorbisComment.user_comments;
        String currComment;

        for (int j = 0; j < ptr.length; j++) {
            if (ptr[j] == null) {
                break;
            }
            currComment = (new String(ptr[j], 0, ptr[j].length - 1)).trim();
            if (TDebug.TraceAudioConverter) {
                TDebug.out("Comment: " + currComment);
            }
        }
        convsize = bufferSize_ / vorbisInfo.channels;
        vorbisDspState.synthesis_init(vorbisInfo);
        vorbisBlock.init(vorbisDspState);
        _pcmf = new float[1][][];
        _index = new int[vorbisInfo.channels];
    }

    /**
     * Reads from the oggBitStream_ a specified number of Bytes(bufferSize_)
     * worth starting at index and puts them in the specified buffer[].
     *
     * @param buffer
     * @param index
     * @param bufferSize_
     * @return the number of bytes read or -1 if error.
     */
    private int readFromStream(byte[] buffer, int index, int bufferSize_) {
        int bytes;
        try {
            bytes = oggBitStream_.read(buffer, index, bufferSize_);
        } catch (Exception e) {
            if (TDebug.TraceAudioConverter) {
                TDebug.out("Cannot Read Selected Song");
            }
            bytes = -1;
        }
        currentBytes += bytes;
        return bytes;
    }

    /**
     * Close the stream.
     */
    @Override
    public void close() throws IOException {
        super.close();
        oggBitStream_.close();
    }
    private static final Logger LOG = Logger.getLogger(DecodedVorbisAudioInputStream.class.getName());
}
TOP

Related Classes of org.vorbis.spi.sampled.convert.DecodedVorbisAudioInputStream

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.