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

com.jcraft.jorbis.VorbisFile Maven / Gradle / Ivy

/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/* JOrbis
 * Copyright (C) 2000 ymnk, JCraft,Inc.
 *  
 * Written by: 2000 ymnk
 *   
 * Many thanks to 
 *   Monty  and 
 *   The XIPHOPHORUS Company http://www.xiph.org/ .
 * JOrbis has been based on their awesome works, Vorbis codec.
 *   
 * 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 com.jcraft.jorbis;

import java.io.IOException;
import java.io.InputStream;

import com.jcraft.jogg.Packet;
import com.jcraft.jogg.Page;
import com.jcraft.jogg.StreamState;
import com.jcraft.jogg.SyncState;

public class VorbisFile{
  static final int CHUNKSIZE=8500;
  static final int SEEK_SET=0;
  static final int SEEK_CUR=1;
  static final int SEEK_END=2;

  static final int OV_FALSE=-1;
  static final int OV_EOF=-2;
  static final int OV_HOLE=-3;

  static final int OV_EREAD=-128;
  static final int OV_EFAULT=-129;
  static final int OV_EIMPL=-130;
  static final int OV_EINVAL=-131;
  static final int OV_ENOTVORBIS=-132;
  static final int OV_EBADHEADER=-133;
  static final int OV_EVERSION=-134;
  static final int OV_ENOTAUDIO=-135;
  static final int OV_EBADPACKET=-136;
  static final int OV_EBADLINK=-137;
  static final int OV_ENOSEEK=-138;

  InputStream datasource;
  boolean seekable=false;
  long offset;
  long end;

  SyncState oy=new SyncState();

  int links;
  long[] offsets;
  long[] dataoffsets;
  int[] serialnos;
  long[] pcmlengths;
  Info[] vi;
  Comment[] vc;

  // Decoding working state local storage
  long pcm_offset;
  boolean decode_ready=false;

  int current_serialno;
  int current_link;

  float bittrack;
  float samptrack;

  StreamState os=new StreamState(); // take physical pages, weld into a logical
  // stream of packets
  DspState vd=new DspState(); // central working state for 
  // the packet->PCM decoder
  Block vb=new Block(vd); // local working space for packet->PCM decode

  //ov_callbacks callbacks;

  public VorbisFile(String file) throws JOrbisException{
    super();
    InputStream is=null;
    try{
      is=new SeekableInputStream(file);
      int ret=open(is, null, 0);
      if(ret==-1){
        throw new JOrbisException("VorbisFile: open return -1");
      }
    }
    catch(Exception e){
      throw new JOrbisException("VorbisFile: "+e.toString());
    }
    finally{
      if(is!=null){
        try{
          is.close();
        }
        catch(IOException e){
          e.printStackTrace();
        }
      }
    }
  }

  public VorbisFile(InputStream is, byte[] initial, int ibytes)
      throws JOrbisException{
    super();
    int ret=open(is, initial, ibytes);
    if(ret==-1){
    }
  }

  private int get_data(){
    int index=oy.buffer(CHUNKSIZE);
    byte[] buffer=oy.data;
    int bytes=0;
    try{
      bytes=datasource.read(buffer, index, CHUNKSIZE);
    }
    catch(Exception e){
      return OV_EREAD;
    }
    oy.wrote(bytes);
    if(bytes==-1){
      bytes=0;
    }
    return bytes;
  }

  private void seek_helper(long offst){
    fseek(datasource, offst, SEEK_SET);
    this.offset=offst;
    oy.reset();
  }

  private int get_next_page(Page page, long boundary){
    if(boundary>0)
      boundary+=offset;
    while(true){
      int more;
      if(boundary>0&&offset>=boundary)
        return OV_FALSE;
      more=oy.pageseek(page);
      if(more<0){
        offset-=more;
      }
      else{
        if(more==0){
          if(boundary==0)
            return OV_FALSE;
          int ret=get_data();
          if(ret==0)
            return OV_EOF;
          if(ret<0)
            return OV_EREAD;
        }
        else{
          int ret=(int)offset; //!!!
          offset+=more;
          return ret;
        }
      }
    }
  }

  private int get_prev_page(Page page) throws JOrbisException{
    long begin=offset; //!!!
    int ret;
    int offst=-1;
    while(offst==-1){
      begin-=CHUNKSIZE;
      if(begin<0)
        begin=0;
      seek_helper(begin);
      while(offset=0)
          next=ret;
      }
      else{
        searched=ret+page.header_len+page.body_len;
      }
    }
    seek_helper(next);
    ret=get_next_page(page, -1);
    if(ret==OV_EREAD)
      return OV_EREAD;

    if(searched>=end||ret==-1){
      links=m+1;
      offsets=new long[m+2];
      offsets[m+1]=searched;
    }
    else{
      ret=bisect_forward_serialno(next, offset, end, page.serialno(), m+1);
      if(ret==OV_EREAD)
        return OV_EREAD;
    }
    offsets[m]=begin;
    return 0;
  }

  // uses the local ogg_stream storage in vf; this is important for
  // non-streaming input sources
  int fetch_headers(Info vi, Comment vc, int[] serialno, Page og_ptr){
    Page og=new Page();
    Packet op=new Packet();
    int ret;

    if(og_ptr==null){
      ret=get_next_page(og, CHUNKSIZE);
      if(ret==OV_EREAD)
        return OV_EREAD;
      if(ret<0)
        return OV_ENOTVORBIS;
      og_ptr=og;
    }

    if(serialno!=null)
      serialno[0]=og_ptr.serialno();

    os.init(og_ptr.serialno());

    // extract the initial header from the first page and verify that the
    // Ogg bitstream is in fact Vorbis data

    vi.init();
    vc.init();

    int i=0;
    while(i<3){
      os.pagein(og_ptr);
      while(i<3){
        int result=os.packetout(op);
        if(result==0)
          break;
        if(result==-1){
          vi.clear();
          vc.clear();
          os.clear();
          return -1;
        }
        if(vi.synthesis_headerin(vc, op)!=0){
          vi.clear();
          vc.clear();
          os.clear();
          return -1;
        }
        i++;
      }
      if(i<3)
        if(get_next_page(og_ptr, 1)<0){
          vi.clear();
          vc.clear();
          os.clear();
          return -1;
        }
    }
    return 0;
  }

  // last step of the OggVorbis_File initialization; get all the
  // vorbis_info structs and PCM positions.  Only called by the seekable
  // initialization (local stream storage is hacked slightly; pay
  // attention to how that's done)
  void prefetch_all_headers(Info first_i, Comment first_c, int dataoffset)
      throws JOrbisException{
    Page og=new Page();
    int ret;

    vi=new Info[links];
    vc=new Comment[links];
    dataoffsets=new long[links];
    pcmlengths=new long[links];
    serialnos=new int[links];

    for(int i=0; i0){
          // got a packet.  process it
          granulepos=op.granulepos;
          if(vb.synthesis(op)==0){ // lazy check for lazy
            // header handling.  The
            // header packets aren't
            // audio, so if/when we
            // submit them,
            // vorbis_synthesis will
            // reject them
            // suck in the synthesis data and track bitrate
            {
              int oldsamples=vd.synthesis_pcmout(null, null);
              vd.synthesis_blockin(vb);
              samptrack+=vd.synthesis_pcmout(null, null)-oldsamples;
              bittrack+=op.bytes*8;
            }

            // update the pcm offset.
            if(granulepos!=-1&&op.e_o_s==0){
              int link=(seekable ? current_link : 0);
              int samples;
              // this packet has a pcm_offset on it (the last packet
              // completed on a page carries the offset) After processing
              // (above), we know the pcm position of the *last* sample
              // ready to be returned. Find the offset of the *first*
              // 
              // As an aside, this trick is inaccurate if we begin
              // reading anew right at the last page; the end-of-stream
              // granulepos declares the last frame in the stream, and the
              // last packet of the last page may be a partial frame.
              // So, we need a previous granulepos from an in-sequence page
              // to have a reference point.  Thus the !op.e_o_s clause above

              samples=vd.synthesis_pcmout(null, null);
              granulepos-=samples;
              for(int i=0; i=links)
      return (-1);
    if(!seekable&&i!=0)
      return (bitrate(0));
    if(i<0){
      long bits=0;
      for(int j=0; j0){
          return vi[i].bitrate_nominal;
        }
        else{
          if(vi[i].bitrate_upper>0){
            if(vi[i].bitrate_lower>0){
              return (vi[i].bitrate_upper+vi[i].bitrate_lower)/2;
            }
            else{
              return vi[i].bitrate_upper;
            }
          }
          return (-1);
        }
      }
    }
  }

  // returns the actual bitrate since last call.  returns -1 if no
  // additional data to offer since last call (or at beginning of stream)
  public int bitrate_instant(){
    int _link=(seekable ? current_link : 0);
    if(samptrack==0)
      return (-1);
    int ret=(int)(bittrack/samptrack*vi[_link].rate+.5);
    bittrack=0.f;
    samptrack=0.f;
    return (ret);
  }

  public int serialnumber(int i){
    if(i>=links)
      return (-1);
    if(!seekable&&i>=0)
      return (serialnumber(-1));
    if(i<0){
      return (current_serialno);
    }
    else{
      return (serialnos[i]);
    }
  }

  // returns: total raw (compressed) length of content if i==-1
  //          raw (compressed) length of that logical bitstream for i==0 to n
  //          -1 if the stream is not seekable (we can't know the length)

  public long raw_total(int i){
    if(!seekable||i>=links)
      return (-1);
    if(i<0){
      long acc=0; // bug?
      for(int j=0; j=links)
      return (-1);
    if(i<0){
      long acc=0;
      for(int j=0; j=links)
      return (-1);
    if(i<0){
      float acc=0;
      for(int j=0; joffsets[links]){
      //goto seek_error;
      pcm_offset=-1;
      decode_clear();
      return -1;
    }

    // clear out decoding machine state
    pcm_offset=-1;
    decode_clear();

    // seek
    seek_helper(pos);

    // we need to make sure the pcm_offset is set.  We use the
    // _fetch_packet helper to process one packet with readp set, then
    // call it until it returns '0' with readp not set (the last packet
    // from a page has the 'granulepos' field set, and that's how the
    // helper updates the offset

    switch(process_packet(1)){
      case 0:
        // oh, eof. There are no packets remaining.  Set the pcm offset to
        // the end of file
        pcm_offset=pcm_total(-1);
        return (0);
      case -1:
        // error! missing data or invalid bitstream structure
        //goto seek_error;
        pcm_offset=-1;
        decode_clear();
        return -1;
      default:
        // all OK
        break;
    }
    while(true){
      switch(process_packet(0)){
        case 0:
          // the offset is set.  If it's a bogus bitstream with no offset
          // information, it's not but that's not our fault.  We still run
          // gracefully, we're just missing the offset
          return (0);
        case -1:
          // error! missing data or invalid bitstream structure
          //goto seek_error;
          pcm_offset=-1;
          decode_clear();
          return -1;
        default:
          // continue processing packets
          break;
      }
    }

    // seek_error:
    // dump the machine so we're in a known state
    //pcm_offset=-1;
    //decode_clear();
    //return -1;
  }

  // seek to a sample offset relative to the decompressed pcm stream 
  // returns zero on success, nonzero on failure

  public int pcm_seek(long pos){
    int link=-1;
    long total=pcm_total(-1);

    if(!seekable)
      return (-1); // don't dump machine if we can't seek
    if(pos<0||pos>total){
      //goto seek_error;
      pcm_offset=-1;
      decode_clear();
      return -1;
    }

    // which bitstream section does this pcm offset occur in?
    for(link=links-1; link>=0; link--){
      total-=pcmlengths[link];
      if(pos>=total)
        break;
    }

    // search within the logical bitstream for the page with the highest
    // pcm_pos preceeding (or equal to) pos.  There is a danger here;
    // missing pages or incorrect frame number information in the
    // bitstream could make our task impossible.  Account for that (it
    // would be an error condition)
    {
      long target=pos-total;
      long end=offsets[link+1];
      long begin=offsets[link];
      int best=(int)begin;

      Page og=new Page();
      while(begin=pos){
      //goto seek_error;
      pcm_offset=-1;
      decode_clear();
      return -1;
    }
    if(pos>pcm_total(-1)){
      //goto seek_error;
      pcm_offset=-1;
      decode_clear();
      return -1;
    }

    // discard samples until we reach the desired position. Crossing a
    // logical bitstream boundary with abandon is OK.
    while(pcm_offsettarget)
        samples=target;
      vd.synthesis_read(samples);
      pcm_offset+=samples;

      if(samplestime_total){
      //goto seek_error;
      pcm_offset=-1;
      decode_clear();
      return -1;
    }

    // which bitstream section does this time offset occur in?
    for(link=links-1; link>=0; link--){
      pcm_total-=pcmlengths[link];
      time_total-=time_total(link);
      if(seconds>=time_total)
        break;
    }

    // enough information to convert time offset to pcm offset
    {
      long target=(long)(pcm_total+(seconds-time_total)*vi[link].rate);
      return (pcm_seek(target));
    }

    //seek_error:
    // dump machine so we're in a known state
    //pcm_offset=-1;
    //decode_clear();
    //return -1;
  }

  // tell the current stream offset cursor.  Note that seek followed by
  // tell will likely not give the set offset due to caching
  public long raw_tell(){
    return (offset);
  }

  // return PCM offset (sample) of next PCM sample to be read
  public long pcm_tell(){
    return (pcm_offset);
  }

  // return time offset (seconds) of next PCM sample to be read
  public float time_tell(){
    // translate time to PCM position and call pcm_seek

    int link=-1;
    long pcm_total=0;
    float time_total=0.f;

    if(seekable){
      pcm_total=pcm_total(-1);
      time_total=time_total(-1);

      // which bitstream section does this time offset occur in?
      for(link=links-1; link>=0; link--){
        pcm_total-=pcmlengths[link];
        time_total-=time_total(link);
        if(pcm_offset>=pcm_total)
          break;
      }
    }

    return ((float)time_total+(float)(pcm_offset-pcm_total)/vi[link].rate);
  }

  //  link:   -1) return the vorbis_info struct for the bitstream section
  //              currently being decoded
  //         0-n) to request information for a specific bitstream section
  //
  // In the case of a non-seekable bitstream, any call returns the
  // current bitstream.  NULL in the case that the machine is not
  // initialized

  public Info getInfo(int link){
    if(seekable){
      if(link<0){
        if(decode_ready){
          return vi[current_link];
        }
        else{
          return null;
        }
      }
      else{
        if(link>=links){
          return null;
        }
        else{
          return vi[link];
        }
      }
    }
    else{
      if(decode_ready){
        return vi[0];
      }
      else{
        return null;
      }
    }
  }

  public Comment getComment(int link){
    if(seekable){
      if(link<0){
        if(decode_ready){
          return vc[current_link];
        }
        else{
          return null;
        }
      }
      else{
        if(link>=links){
          return null;
        }
        else{
          return vc[link];
        }
      }
    }
    else{
      if(decode_ready){
        return vc[0];
      }
      else{
        return null;
      }
    }
  }

  int host_is_big_endian(){
    return 1;
    //    short pattern = 0xbabe;
    //    unsigned char *bytewise = (unsigned char *)&pattern;
    //    if (bytewise[0] == 0xba) return 1;
    //    assert(bytewise[0] == 0xbe);
    //    return 0;
  }

  // up to this point, everything could more or less hide the multiple
  // logical bitstream nature of chaining from the toplevel application
  // if the toplevel application didn't particularly care.  However, at
  // the point that we actually read audio back, the multiple-section
  // nature must surface: Multiple bitstream sections do not necessarily
  // have to have the same number of channels or sampling rate.
  // 
  // read returns the sequential logical bitstream number currently
  // being decoded along with the PCM data in order that the toplevel
  // application can take action on channel/sample rate changes.  This
  // number will be incremented even for streamed (non-seekable) streams
  // (for seekable streams, it represents the actual logical bitstream
  // index within the physical bitstream.  Note that the accessor
  // functions above are aware of this dichotomy).
  //
  // input values: buffer) a buffer to hold packed PCM data for return
  //               length) the byte length requested to be placed into buffer
  //               bigendianp) should the data be packed LSB first (0) or
  //                           MSB first (1)
  //               word) word size for output.  currently 1 (byte) or 
  //                     2 (16 bit short)
  // 
  // return values: -1) error/hole in data
  //                 0) EOF
  //                 n) number of bytes of PCM actually returned.  The
  //                    below works on a packet-by-packet basis, so the
  //                    return length is not related to the 'length' passed
  //                    in, just guaranteed to fit.
  // 
  // *section) set to the logical bitstream number

  int read(byte[] buffer, int length, int bigendianp, int word, int sgned,
      int[] bitstream){
    int host_endian=host_is_big_endian();
    int index=0;

    while(true){
      if(decode_ready){
        float[][] pcm;
        float[][][] _pcm=new float[1][][];
        int[] _index=new int[getInfo(-1).channels];
        int samples=vd.synthesis_pcmout(_pcm, _index);
        pcm=_pcm[0];
        if(samples!=0){
          // yay! proceed to pack data into the byte buffer
          int channels=getInfo(-1).channels;
          int bytespersample=word*channels;
          if(samples>length/bytespersample)
            samples=length/bytespersample;

          // a tight loop to pack each size
          {
            int val;
            if(word==1){
              int off=(sgned!=0 ? 0 : 128);
              for(int j=0; j127)
                    val=127;
                  else if(val<-128)
                    val=-128;
                  buffer[index++]=(byte)(val+off);
                }
              }
            }
            else{
              int off=(sgned!=0 ? 0 : 32768);

              if(host_endian==bigendianp){
                if(sgned!=0){
                  for(int i=0; i32767)
                        val=32767;
                      else if(val<-32768)
                        val=-32768;
                      buffer[dest]=(byte)(val>>>8);
                      buffer[dest+1]=(byte)(val);
                      dest+=channels*2;
                    }
                  }
                }
                else{
                  for(int i=0; i32767)
                        val=32767;
                      else if(val<-32768)
                        val=-32768;
                      buffer[dest]=(byte)((val+off)>>>8);
                      buffer[dest+1]=(byte)(val+off);
                      dest+=channels*2;
                    }
                  }
                }
              }
              else if(bigendianp!=0){
                for(int j=0; j32767)
                      val=32767;
                    else if(val<-32768)
                      val=-32768;
                    val+=off;
                    buffer[index++]=(byte)(val>>>8);
                    buffer[index++]=(byte)val;
                  }
                }
              }
              else{
                //int val;
                for(int j=0; j32767)
                      val=32767;
                    else if(val<-32768)
                      val=-32768;
                    val+=off;
                    buffer[index++]=(byte)val;
                    buffer[index++]=(byte)(val>>>8);
                  }
                }
              }
            }
          }

          vd.synthesis_read(samples);
          pcm_offset+=samples;
          if(bitstream!=null)
            bitstream[0]=current_link;
          return (samples*bytespersample);
        }
      }

      // suck in another packet
      switch(process_packet(1)){
        case 0:
          return (0);
        case -1:
          return -1;
        default:
          break;
      }
    }
  }

  public Info[] getInfo(){
    return vi;
  }

  public Comment[] getComment(){
    return vc;
  }

  public void close() throws java.io.IOException{
    datasource.close();
  }

  class SeekableInputStream extends InputStream{
    java.io.RandomAccessFile raf=null;
    final String mode="r";

    SeekableInputStream(String file) throws java.io.IOException{
      raf=new java.io.RandomAccessFile(file, mode);
    }

    public int read() throws java.io.IOException{
      return raf.read();
    }

    public int read(byte[] buf) throws java.io.IOException{
      return raf.read(buf);
    }

    public int read(byte[] buf, int s, int len) throws java.io.IOException{
      return raf.read(buf, s, len);
    }

    public long skip(long n) throws java.io.IOException{
      return (long)(raf.skipBytes((int)n));
    }

    public long getLength() throws java.io.IOException{
      return raf.length();
    }

    public long tell() throws java.io.IOException{
      return raf.getFilePointer();
    }

    public int available() throws java.io.IOException{
      return (raf.length()==raf.getFilePointer()) ? 0 : 1;
    }

    public void close() throws java.io.IOException{
      raf.close();
    }

    public synchronized void mark(int m){
    }

    public synchronized void reset() throws java.io.IOException{
    }

    public boolean markSupported(){
      return false;
    }

    public void seek(long pos) throws java.io.IOException{
      raf.seek(pos);
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy