diff options
Diffstat (limited to 'songdbj/com/jcraft/jorbis/VorbisFile.java.new')
| -rw-r--r-- | songdbj/com/jcraft/jorbis/VorbisFile.java.new | 1240 |
1 files changed, 1240 insertions, 0 deletions
diff --git a/songdbj/com/jcraft/jorbis/VorbisFile.java.new b/songdbj/com/jcraft/jorbis/VorbisFile.java.new new file mode 100644 index 0000000..1f822b0 --- /dev/null +++ b/songdbj/com/jcraft/jorbis/VorbisFile.java.new @@ -0,0 +1,1240 @@ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk<ymnk@jcraft.com> + * + * Many thanks to + * Monty <monty@xiph.org> 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; + +public class VorbisFile{ + static final int CHUNKSIZE=4096; + static final int SEEK_SET=0; + + InputStream datasource; + boolean seekable=false; + long offset; + long end; + + SyncState oy=new SyncState(); + + int links; + Comment[] vc; + Info[] vi; + + long[] offsets; + long[] dataoffsets; + int[] serialnos; + long[] pcmlengths; + + + + // 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 java.io.BufferedInputStream(new java.io.FileInputStream(file));} + catch(Exception e){ + throw new JOrbisException("VorbisFile: "+e.toString()); + } + int ret=open(is, null, 0); + if(ret==-1){ + throw new JOrbisException("VorbisFile: open return -1"); + } + } + + 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);} + oy.wrote(bytes); + return bytes; + } + + private void seek_helper(int offst){ + //callbacks.seek_func(datasource, offst, SEEK_SET); + fseek64_wrap(datasource, offst, SEEK_SET); + this.offset=offst; + oy.reset(); + } + + private int get_next_page(Page page, int boundary){ + if(boundary>0) boundary+=offset; + while(true){ + int more; + if(boundary>0 && offset>=boundary)return -1; + more=oy.pageseek(page); + if(more<0){offset-=more;} + else{ + if(more==0){ + if(boundary==0)return -1; + if(get_data()<=0)return -1; + } + else{ + int ret=(int)offset; //!!! + offset+=more; + return ret; + } + } + } + } + + private int get_prev_page(Page page){ + int begin=(int)offset; //!!! + int ret; + int offst=-1; + while(offst==-1){ + begin-=CHUNKSIZE; + seek_helper(begin); + while(offset<begin+CHUNKSIZE){ + ret=get_next_page(page, begin+CHUNKSIZE-((int)offset)); + if(ret==-1){ break; } + else{ offst=ret; } + } + } + seek_helper((int)offset); //!!! + ret=get_next_page(page, CHUNKSIZE); + if(ret==-1){ + System.err.println("Missed page fencepost at end of logical bitstream Exiting"); + System.exit(1); + } + return offst; + } + + void bisect_forward_serialno(int begin, int searched, int end, int currentno, int m){ + int endsearched=end; + int next=end; + Page page=new Page(); + int ret; + while(searched<endsearched){ + int bisect; + if(endsearched-searched<CHUNKSIZE){ + bisect=searched; + } + else{ + bisect=(searched+endsearched)/2; + } + + seek_helper(bisect); + ret=get_next_page(page, -1); + if(ret<0 || page.serialno()!=currentno){ + endsearched=bisect; + if(ret>=0)next=ret; + } + else{ + searched=ret+page.header_len+page.body_len; + } + } + seek_helper(next); + ret=get_next_page(page, -1); + + if(searched>=end || ret==-1){ + links=m+1; + offsets=new long[m+2]; + offsets[m+1]=searched; + } + else{ + bisect_forward_serialno(next, (int)offset, end, page.serialno(), m+1); + } + offsets[m]=begin; + } + + // 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){ + //System.err.println("fetch_headers"); + Page og=new Page(); + Packet op=new Packet(); + int ret; + + ret=get_next_page(og, CHUNKSIZE); + if(ret==-1){ + System.err.println("Did not find initial header for bitstream."); + return -1; + } + + if(serialno!=null)serialno[0]=og.serialno(); + + os.init(og.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); + 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, 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){ + 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;i<links;i++){ + if(first_i!=null && first_c!=null && i==0){ + // we already grabbed the initial header earlier. This just + // saves the waste of grabbing it again + // !!!!!!!!!!!!! + vi[i]=first_i; + //memcpy(vf->vi+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((int)offsets[i]); //!!! + if(fetch_headers(vi[i], vc[i], 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 + { + int end=(int)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(){ + Info initial_i=new Info(); + Comment initial_c=new Comment(); + int serialno,end; + int ret; + int dataoffset; + Page og=new Page(); +System.out.println("open_seekable"); + // is this even vorbis...? + int[] foo=new int[1]; + ret=fetch_headers(initial_i, initial_c, foo); + 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); + fseek64_wrap(datasource, (int)offset, SEEK_SET); + //offset=end=(callbacks.tell_func)(datasource); + end=(int)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 + bisect_forward_serialno(0,0,end+1,serialno,0); + } + else{ + // Only one logical bitstream + bisect_forward_serialno(0,end,end+1,serialno,0); + } + prefetch_all_headers(initial_i, initial_c, dataoffset); + +System.out.println("?"); + 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)==-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){ +System.out.println("porcess_packet:"+ readp+" , decode_ready="+decode_ready); + 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;i<link;i++){ + granulepos+=pcmlengths[i]; + } + pcm_offset=granulepos; + } + return(1); + } + } + } + + if(readp==0)return(0); + if(get_next_page(og,-1)<0)return(0); // eof. leave unitialized + + // bitrate tracking; add the header's bytes here, the body bytes + // are done by packet above + bittrack+=og.header_len*8; + + // has our decoding just traversed a bitstream boundary? + if(decode_ready){ + if(current_serialno!=og.serialno()){ + decode_clear(); + } + } + + // Do we need to load a new machine before submitting the page? + // This is different in the seekable and non-seekable cases. + // + // In the seekable case, we already have all the header + // information loaded and cached; we just initialize the machine + // with it and continue on our merry way. + // + // In the non-seekable (streaming) case, we'll only be at a + // boundary if we just left the previous logical bitstream and + // we're now nominally at the header of the next bitstream + + if(!decode_ready){ + int i; + if(seekable){ + current_serialno=og.serialno(); + + // match the serialno to bitstream section. We use this rather than + // offset positions to avoid problems near logical bitstream + // boundaries + for(i=0;i<links;i++){ + if(serialnos[i]==current_serialno)break; + } + if(i==links)return(-1); // sign of a bogus stream. error out, + // leave machine uninitialized + current_link=i; + + os.init(current_serialno); + os.reset(); + + } + else{ + // we're streaming + // fetch the three header packets, build the info struct + int foo[]=new int[1]; + fetch_headers(vi[0], vc[0], foo); + current_serialno=foo[0]; + current_link++; + i=0; + } + make_decode_ready(); + } + os.pagein(og); + } + } + + //The helpers are over; it's all toplevel interface from here on out + // clear out the OggVorbis_File struct + int clear(){ + vb.clear(); + vd.clear(); + os.clear(); + + if(vi!=null && links!=0){ + for(int i=0;i<links;i++){ + vi[i].clear(); + vc[i].clear(); + } + vi=null; + vc=null; + } + if(dataoffsets!=null)dataoffsets=null; + if(pcmlengths!=null)pcmlengths=null; + if(serialnos!=null)serialnos=null; + if(offsets!=null)offsets=null; + oy.clear(); + //if(datasource!=null)(vf->callbacks.close_func)(vf->datasource); + //memset(vf,0,sizeof(OggVorbis_File)); + return(0); + } + + static int fseek64_wrap(InputStream fis, + //int64_t off, + int off, + int whence){ + + if(!fis.markSupported()){ return -1; } + try{ + try{if(whence==0){ fis.reset(); }} + catch(Exception ee){System.out.println(ee);} + fis.skip(off); + } + catch(Exception e){ System.out.println(e); + //return -1; + } + 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){ + return open_callbacks(is, initial, ibytes//, callbacks + ); + } + + int open_callbacks(InputStream is, byte[] initial, + int ibytes//, callbacks callbacks + ){ +// int offset=callbacks.seek_func(f,0,SEEK_CUR); + int _offset=fseek64_wrap(is, (int)offset, SEEK_SET); + int ret; + // memset(vf,0,sizeof(OggVorbis_File)); + 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); + } + +System.out.println("open_callbacks="+_offset); + // can we seek? Stevens suggests the seek test was portable + if(_offset!=-1){ ret=open_seekable(); } + else{ ret=open_nonseekable(); } + +System.out.println("ret="+ret); + + 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;j<links;j++){ + bits+=(offsets[j+1]-dataoffsets[j])*8; + } + return((int)Math.rint(bits/time_total(-1))); + } + else{ + if(seekable){ + // return the actual bitrate + return((int)Math.rint((offsets[i+1]-dataoffsets[i])*8/time_total(i))); + } + else{ + // return nominal if set + if(vi[i].bitrate_nominal>0){ + 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){ +System.out.println("raw_total: "+seekable); + if(!seekable || i>=links)return(-1); + if(i<0){ + long acc=0; // bug? + for(int j=0;j<links;j++){ + acc+=raw_total(j); + } + return(acc); + } + else{ + return(offsets[i+1]-offsets[i]); + } + } + + // returns: total PCM length (samples) of content if i==-1 + // PCM length (samples) of that logical bitstream for i==0 to n + // -1 if the stream is not seekable (we can't know the length) + public long pcm_total(int i){ + if(!seekable || i>=links)return(-1); + if(i<0){ + long acc=0; + for(int j=0;j<links;j++){ + acc+=pcm_total(j); + } + return(acc); + } + else{ + return(pcmlengths[i]); + } + } + + // returns: total seconds of content if i==-1 + // seconds in that logical bitstream for i==0 to n + // -1 if the stream is not seekable (we can't know the length) + public float time_total(int i){ + if(!seekable || i>=links)return(-1); + if(i<0){ + float acc=0; + for(int j=0;j<links;j++){ + acc+=time_total(j); + } + return(acc); + } + else{ + return((float)(pcmlengths[i])/vi[i].rate); + } + } + + // seek to an offset relative to the *compressed* data. This also + // immediately sucks in and decodes pages to update the PCM cursor. It + // will cross a logical bitstream boundary, but only if it can't get + // any packets out of the tail of the bitstream we seek to (so no + // surprises). + // + // returns zero on success, nonzero on failure + + public int raw_seek(int pos){ +System.out.println("raw_seek: "+pos); + if(!seekable)return(-1); // don't dump machine if we can't seek + if(pos<0 || pos>offsets[links]){ + //goto seek_error; + pcm_offset=-1; + decode_clear(); + return -1; + } +System.out.println("#1"); + // clear out decoding machine state + pcm_offset=-1; +System.out.println("#2"); + decode_clear(); +System.out.println("#3"); + // 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 +System.out.println("#4"); + switch(process_packet(1)){ + case 0: +System.out.println("?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: +System.out.println("?-1"); + // error! missing data or invalid bitstream structure + //goto seek_error; + pcm_offset=-1; + decode_clear(); + return -1; + default: +System.out.println("?break"); + // all OK + break; + } +System.out.println("pcm_offset="+pcm_offset); + 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; + int end=(int)offsets[link+1]; + int begin=(int)offsets[link]; + int best=begin; + + Page og=new Page(); + while(begin<end){ + int bisect; + int ret; + + if(end-begin<CHUNKSIZE){ + bisect=begin; + } + else{ + bisect=(end+begin)/2; + } + + seek_helper(bisect); + ret=get_next_page(og,end-bisect); + + if(ret==-1){ + end=bisect; + } + else{ + long granulepos=og.granulepos(); + if(granulepos<target){ + best=ret; // raw offset of packet with granulepos + begin=(int)offset; // raw offset of next packet + } + else{ + end=bisect; + } + } + } + // found our page. seek to it (call raw_seek). + if(raw_seek(best)!=0){ + //goto seek_error; + pcm_offset=-1; + decode_clear(); + return -1; + } + } + + // verify result + if(pcm_offset>=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_offset<pos){ + float[][] pcm; + int target=(int)(pos-pcm_offset); + float[][][] _pcm=new float[1][][]; + int[] _index=new int[info(-1).channels]; + int samples=vd.synthesis_pcmout(_pcm, _index); + pcm=_pcm[0]; + + if(samples>target)samples=target; + vd.synthesis_read(samples); + pcm_offset+=samples; + + if(samples<target) + if(process_packet(1)==0){ + pcm_offset=pcm_total(-1); // eof + } + } + return 0; + + // seek_error: + // dump machine so we're in a known state + //pcm_offset=-1; + //decode_clear(); + //return -1; + } + + // seek to a playback time relative to the decompressed pcm stream + // returns zero on success, nonzero on failure + public int time_seek(float seconds){ + // translate time to PCM position and call pcm_seek + + int link=-1; + long pcm_total=pcm_total(-1); + float time_total=time_total(-1); + + if(!seekable)return(-1); // don't dump machine if we can't seek + if(seconds<0 || seconds>time_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 info(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 comment(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[info(-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=info(-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;j<samples;j++){ + for(int i=0;i<channels;i++){ + val=(int)(pcm[i][_index[i]+j]*128. + 0.5); + if(val>127)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;i<channels;i++) { // It's faster in this order + int src=_index[i]; + int dest=i; + for(int j=0;j<samples;j++) { + val=(int)(pcm[i][src+j]*32768. + 0.5); + if(val>32767)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;i<channels;i++) { + float[] src=pcm[i]; + int dest=i; + for(int j=0;j<samples;j++) { + val=(int)(src[j]*32768. + 0.5); + if(val>32767)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;j<samples;j++){ + for(int i=0;i<channels;i++){ + val=(int)(pcm[i][j]*32768. + 0.5); + if(val>32767)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;j<samples;j++){ + for(int i=0;i<channels;i++){ + val=(int)(pcm[i][j]*32768. + 0.5); + if(val>32767)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 int getLinks(){return links;} + 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.getLinks(); + System.out.println("links="+links); + Comment[] comment=foo.getComment(); + Info[] info=foo.getInfo(); + for(int i=0; i<links; i++){ + System.out.println(info[i]); + System.out.println(comment[i]); + } + System.out.println("raw_total: "+foo.raw_total(-1)); + System.out.println("pcm_total: "+foo.pcm_total(-1)); + System.out.println("time_total: "+foo.time_total(-1)); + } + catch(Exception e){ + System.err.println(e); + } + } +} |