/* 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 com.jcraft.jogg.*; import java.io.InputStream; import java.io.IOException; 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=callbacks.read_func(buffer, index, 1, CHUNKSIZE, datasource); int bytes=0; try{ bytes=datasource.read(buffer, index, CHUNKSIZE); } catch(Exception e){ //System.err.println(e); return OV_EREAD; } oy.wrote(bytes); if(bytes==-1){ // System.out.println("bytes="+bytes); bytes=0; } return bytes; } private void seek_helper(long offst){ //callbacks.seek_func(datasource, offst, SEEK_SET); 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; // if(get_data()<=0)return -1; 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){ //System.err.println("fetch_headers"); 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){ //System.err.println("Corrupt header in logical bitstream."); //goto bail_header; vi.clear(); vc.clear(); os.clear(); return -1; } if(vi.synthesis_headerin(vc, op)!=0){ //System.err.println("Illegal header in logical bitstream."); //goto bail_header; vi.clear(); vc.clear(); os.clear(); return -1; } i++; } if(i<3) if(get_next_page(og_ptr, 1)<0){ //System.err.println("Missing header in logical bitstream."); //goto bail_header; vi.clear(); vc.clear(); os.clear(); return -1; } } return 0; // bail_header: // vorbis_info_clear(vi); // vorbis_comment_clear(vc); // ogg_stream_clear(&vf->os); // return -1; } // 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;ivi+i,first_i,sizeof(vorbis_info)); vc[i]=first_c; //memcpy(vf->vc+i,first_c,sizeof(vorbis_comment)); dataoffsets[i]=dataoffset; } else{ // seek to the location of the initial header seek_helper(offsets[i]); //!!! vi[i]=new Info(); vc[i]=new Comment(); if(fetch_headers(vi[i], vc[i], null, null)==-1){ //System.err.println("Error opening logical bitstream #"+(i+1)+"\n"); dataoffsets[i]=-1; } else{ dataoffsets[i]=offset; os.clear(); } } // get the serial number and PCM length of this link. To do this, // get the last page of the stream { long end=offsets[i+1]; //!!! seek_helper(end); while(true){ ret=get_prev_page(og); if(ret==-1){ // this should not be possible //System.err.println("Could not find last page of logical "+ // "bitstream #"+(i)+"\n"); vi[i].clear(); vc[i].clear(); break; } if(og.granulepos()!=-1){ serialnos[i]=og.serialno(); pcmlengths[i]=og.granulepos(); break; } } } } } int make_decode_ready(){ if(decode_ready)System.exit(1); vd.synthesis_init(vi[0]); vb.init(vd); decode_ready=true; return(0); } int open_seekable() throws JOrbisException { Info initial_i=new Info(); Comment initial_c=new Comment(); int serialno; long end; int ret; int dataoffset; Page og=new Page(); // is this even vorbis...? int[] foo=new int[1]; ret=fetch_headers(initial_i, initial_c, foo, null); serialno=foo[0]; dataoffset=(int)offset; //!! os.clear(); if(ret==-1)return(-1); // we can seek, so set out learning all about this file seekable=true; //(callbacks.seek_func)(datasource, 0, SEEK_END); fseek(datasource, 0, SEEK_END); //offset=end=(callbacks.tell_func)(datasource); offset=ftell(datasource); end=offset; // We get the offset for the last page of the physical bitstream. // Most OggVorbis files will contain a single logical bitstream end=get_prev_page(og); // moer than one logical bitstream? if(og.serialno()!=serialno){ // Chained bitstream. Bisect-search each logical bitstream // section. Do so based on serial number only if(bisect_forward_serialno(0,0,end+1,serialno,0)<0){ clear(); return OV_EREAD; } } else{ // Only one logical bitstream if(bisect_forward_serialno(0,end,end+1,serialno,0)<0){ clear(); return OV_EREAD; } } prefetch_all_headers(initial_i, initial_c, dataoffset); return(raw_seek(0)); } int open_nonseekable(){ //System.err.println("open_nonseekable"); // we cannot seek. Set up a 'single' (current) logical bitstream entry links=1; vi=new Info[links]; vi[0]=new Info(); // ?? vc=new Comment[links]; vc[0]=new Comment(); // ?? bug? // Try to fetch the headers, maintaining all the storage int[]foo=new int[1]; if(fetch_headers(vi[0], vc[0], foo, null)==-1)return(-1); current_serialno=foo[0]; make_decode_ready(); return 0; } // clear out the current logical bitstream decoder void decode_clear(){ os.clear(); vd.clear(); vb.clear(); decode_ready=false; bittrack=0.f; samptrack=0.f; } // fetch and process a packet. Handles the case where we're at a // bitstream boundary and dumps the decoding machine. If the decoding // machine is unloaded, it loads it. It also keeps pcm_offset up to // date (seek and read both use this. seek uses a special hack with // readp). // // return: -1) hole in the data (lost packet) // 0) need more date (only if readp==0)/eof // 1) got a packet int process_packet(int readp){ Page og=new Page(); // handle one packet. Try to fetch it from current stream state // extract packets from page while(true){ // process a packet if we can. If the machine isn't loaded, // neither is a page if(decode_ready){ Packet op=new Packet(); int result=os.packetout(op); long granulepos; // if(result==-1)return(-1); // hole in the data. For now, swallow // and go. We'll need to add a real // error code in a bit. if(result>0){ // 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;icallbacks.close_func)(vf->datasource); //memset(vf,0,sizeof(OggVorbis_File)); return(0); } static int fseek(InputStream fis, //int64_t off, long off, int whence){ if(fis instanceof SeekableInputStream){ SeekableInputStream sis=(SeekableInputStream)fis; try{ if(whence==SEEK_SET){ sis.seek(off); } else if(whence==SEEK_END){ sis.seek(sis.getLength()-off); } else{ //System.out.println("seek: "+whence+" is not supported"); } } catch(Exception e){ } return 0; } try{ if(whence==0){ fis.reset(); } fis.skip(off); } catch(Exception e){return -1;} return 0; } static long ftell(InputStream fis){ try{ if(fis instanceof SeekableInputStream){ SeekableInputStream sis=(SeekableInputStream)fis; return (sis.tell()); } } catch(Exception e){ } return 0; } // inspects the OggVorbis file and finds/documents all the logical // bitstreams contained in it. Tries to be tolerant of logical // bitstream sections that are truncated/woogie. // // return: -1) error // 0) OK int open(InputStream is, byte[] initial, int ibytes) throws JOrbisException { //callbacks callbacks = { // (size_t (*)(void *, size_t, size_t, void *)) fread, // (int (*)(void *, int64_t, int)) _fseek, // (int (*)(void *)) fclose, // (long (*)(void *)) ftell // }; return open_callbacks(is, initial, ibytes//, callbacks ); } int open_callbacks(InputStream is, byte[] initial, int ibytes//, callbacks callbacks ) throws JOrbisException { int ret; datasource=is; //callbacks = _callbacks; // init the framing state oy.init(); // perhaps some data was previously read into a buffer for testing // against other stream types. Allow initialization from this // previously read data (as we may be reading from a non-seekable // stream) if(initial!=null){ int index=oy.buffer(ibytes); System.arraycopy(initial, 0, oy.data, index, ibytes); oy.wrote(ibytes); } // can we seek? Stevens suggests the seek test was portable if(is instanceof SeekableInputStream){ ret=open_seekable(); } else{ ret=open_nonseekable(); } if(ret!=0){ datasource=null; clear(); } return ret; } // How many logical bitstreams in this physical bitstream? public int streams(){ return links; } // Is the FILE * associated with vf seekable? public boolean seekable(){ return seekable; } // returns the bitrate for a given logical bitstream or the entire // physical bitstream. If the file is open for random access, it will // find the *actual* average bitrate. If the file is streaming, it // returns the nominal bitrate (if set) else the average of the // upper/lower bounds (if set) else -1 (unset). // // If you want the actual bitrate field settings, get them from the // vorbis_info structs public int bitrate(int i){ if(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 static void main(String[] arg){ try{ VorbisFile foo=new VorbisFile(arg[0]); int links=foo.streams(); System.out.println("links="+links); Comment[] comment=foo.getComment(); Info[] info=foo.getInfo(); for(int i=0; i