| /* 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); |
| } |
| } |
| } |