All Downloads are FREE. Search and download functionalities are using the official Maven repository.

javazoom.spi.vorbis.sampled.convert.DecodedVorbisAudioInputStream Maven / Gradle / Ivy

Go to download

Maven artifact for VorbisSPI library. http://www.javazoom.net/vorbisspi/vorbisspi.html

The newest version!
/*
 *   DecodedVorbisAudioInputStream
 *   
 *    JavaZOOM : [email protected]
 *               http://www.javazoom.net
 *
 * ----------------------------------------------------------------------------
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU Library General Public License as published
 *   by the Free Software Foundation; either version 2 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 Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 * ----------------------------------------------------------------------------
 */
 
package javazoom.spi.vorbis.sampled.convert;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;

import javazoom.spi.PropertiesContainer;

import org.tritonus.share.TDebug;
import org.tritonus.share.sampled.convert.TAsynchronousFilteredAudioInputStream;

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;

/**
 * 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.
   * 
   * 
    *
  • ogg.position.byte [Long], current position in bytes in the stream. *
*/ public Map properties() { properties.put("ogg.position.byte",new Long(currentBytes)); return properties; } /** * Main loop. */ 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 = 0.0; // 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 = 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 = 0; try { bytes = oggBitStream_.read(buffer, index, bufferSize_); } catch(Exception e) { if(TDebug.TraceAudioConverter) TDebug.out("Cannot Read Selected Song"); bytes = -1; } currentBytes = currentBytes + bytes; return bytes; } /** * Close the stream. */ public void close() throws IOException { super.close(); oggBitStream_.close(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy