function: stdio-based convenience library for opening/seeking/decoding
author: Monty <xiphmont@mit.edu>
modifications by: Monty
- last modification date: Nov 02 1999
+ last modification date: Nov 04 1999
********************************************************************/
}
static int _open_nonseekable(OggVorbis_File *vf){
- vorbis_info vi;
- long serialno;
-
- /* Try to fetch the headers, maintaining all the storage */
- if(_fetch_headers(vf,&vi,&serialno)==-1)return(-1);
-
/* we cannot seek. Set up a 'single' (current) logical bitstream entry */
vf->links=1;
vf->vi=malloc(sizeof(vorbis_info));
- vf->serialnos=malloc(sizeof(long));
-
- memcpy(vf->vi,&vi,sizeof(vorbis_info));
- vf->serialnos[0]=serialno;
+
+ /* Try to fetch the headers, maintaining all the storage */
+ if(_fetch_headers(vf,vf->vi,&vf->current_serialno)==-1)return(-1);
+
return 0;
}
-
+
+/* clear out the current logical bitstream decoder */
+static void _decode_clear(OggVorbis_File *vf){
+ ogg_stream_clear(&vf->os);
+ vorbis_dsp_clear(&vf->vd);
+ vorbis_block_clear(&vf->vb);
+ vf->pcm_offset=-1;
+ vf->decode_ready=0;
+}
+
+/* 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
+*/
+
+static int _process_packet(OggVorbis_File *vf,int readp){
+ ogg_page og;
+
+ /* handle one packet. Try to fetch it from current stream state */
+ /* extract packets from page */
+ while(1){
+
+ /* process a packet if we can. If the machine isn't loaded,
+ neither is a page */
+ if(vf->decode_ready){
+ ogg_packet op;
+ int result=ogg_stream_packetout(&vf->os,&op);
+ size64 frameno;
+
+ if(result==-1)return(-1); /* hole in the data. alert the toplevel */
+ if(result>0){
+ /* got a packet. process it */
+ frameno=op.frameno;
+ vorbis_synthesis(&vf->vb,&op);
+ vorbis_synthesis_blockin(&vf->vd,&vf->vb);
+
+ /* update the pcm offset. */
+ if(frameno!=-1){
+ int link=(vf->seekable?vf->current_link:0);
+ double **dummy;
+ int i,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* */
+
+ samples=vorbis_synthesis_pcmout(&vf->vd,&dummy);
+
+ frameno-=samples;
+ for(i=0;i<link;i++)
+ frameno+=vf->pcmlengths[i];
+ vf->pcm_offset=frameno;
+ }
+ return(1);
+ }
+ }
+
+ if(!readp)return(0);
+ if(_get_next_page(vf,&og,-1)<0)return(0); /* eof. leave unitialized */
+
+ /* has our decoding just traversed a bitstream boundary? */
+ if(vf->decode_ready){
+ if(vf->current_serialno!=ogg_page_serialno(&og)){
+ _decode_clear(vf);
+ }
+ }
+
+ /* 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(!vf->decode_ready){
+ int link;
+ if(vf->seekable){
+ vf->current_serialno=ogg_page_serialno(&og);
+
+ /* match the serialno to bitstream section. We use this rather than
+ offset positions to avoid problems near logical bitstream
+ boundaries */
+ for(link=0;link<vf->links;link++)
+ if(vf->serialnos[link]==vf->current_serialno)break;
+ if(link==vf->links)return(-1); /* sign of a bogus stream. error out,
+ leave machine uninitialized */
+
+ vf->current_link=link;
+ }else{
+ /* we're streaming */
+ /* fetch the three header packets, build the info struct */
+
+ _fetch_headers(vf,vf->vi,&vf->current_serialno);
+ vf->current_link++;
+ link=0;
+ }
+
+ /* reload */
+ ogg_stream_init(&vf->os,vf->current_serialno);
+ vorbis_synthesis_init(&vf->vd,vf->vi+link);
+ vorbis_block_init(&vf->vd,&vf->vb);
+ vf->decode_ready=1;
+ }
+ ogg_stream_pagein(&vf->os,&og);
+ }
+}
+
/**********************************************************************
* The helpers are over; it's all toplevel interface from here on out */
if(ret){
vf->f=NULL;
ov_clear(vf);
+ }else{
+ ogg_stream_init(&vf->os,vf->current_serialno);
+ vorbis_synthesis_init(&vf->vd,vf->vi);
+ vorbis_block_init(&vf->vd,&vf->vb);
+ vf->decode_ready=1;
}
return(ret);
}
-double ov_lbtime(OggVorbis_File *vf,int i){
- if(i>=0 && i<vf->links)
- return((float)(vf->pcmlengths[i])/vf->vi[i].rate);
- return(0);
+long ov_streams(OggVorbis_File *vf){
+ return vf->links;
}
-double ov_totaltime(OggVorbis_File *vf){
- double acc=0;
- int i;
- for(i=0;i<vf->links;i++)
- acc+=ov_lbtime(vf,i);
- return(acc);
+long ov_seekable(OggVorbis_File *vf){
+ return vf->seekable;
}
-/* seek to an offset relative to the *compressed* data */
-int ov_seek_stream(OggVorbis_File *vf,long pos){
+long ov_raw_total(OggVorbis_File *vf,int i){
+ if(!vf->seekable)return(-1);
+ if(i<0 || i>=vf->links){
+ long acc=0;
+ int i;
+ for(i=0;i<vf->links;i++)
+ acc+=ov_raw_total(vf,i);
+ return(acc);
+ }else{
+ return(vf->offsets[i+1]-vf->offsets[i]);
+ }
+}
+size64 ov_pcm_total(OggVorbis_File *vf,int i){
+ if(!vf->seekable)return(-1);
+ if(i<0 || i>=vf->links){
+ size64 acc=0;
+ int i;
+ for(i=0;i<vf->links;i++)
+ acc+=ov_pcm_total(vf,i);
+ return(acc);
+ }else{
+ return(vf->pcmlengths[i]);
+ }
+}
+double ov_time_total(OggVorbis_File *vf,int i){
+ if(!vf->seekable)return(-1);
+ if(i<0 || i>=vf->links){
+ double acc=0;
+ int i;
+ for(i=0;i<vf->links;i++)
+ acc+=ov_time_total(vf,i);
+ return(acc);
+ }else{
+ return((float)(vf->pcmlengths[i])/vf->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). */
+
+int ov_raw_seek(OggVorbis_File *vf,long pos){
+ int link;
+
+ if(!vf->seekable)return(-1); /* don't dump machine if we can't seek */
+ if(pos<0 || pos>vf->offsets[vf->links])goto seek_error;
+
+ /* clear out decoding machine state */
+ _decode_clear(vf);
+
+ /* seek */
+ _seek_helper(vf,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 'frameno' field set, and that's how the
+ helper updates the offset */
+
+ switch(_process_packet(vf,1)){
+ case 0:
+ /* oh, eof. There are no packets remaining. Set the pcm offset to
+ the end of file */
+ vf->pcm_offset=ov_pcm_total(vf,-1);
+ return(0);
+ case -1:
+ /* error! missing data or invalid bitstream structure */
+ goto seek_error;
+ default:
+ /* all OK */
+ break;
+ }
+ while(1){
+ switch(_process_packet(vf,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;
+ default:
+ /* continue processing packets */
+ break;
+ }
+ }
+
+ seek_error:
+ /* dump the machine so we're in a known state */
+ _decode_clear(vf);
+ return -1;
}
-/* seek to the beginning of the next logical bitstream within the
- physical bitstream */
-int ov_seek_bitstream(OggVorbis_File *vf,long pos){
+/* seek to a sample offset relative to the decompressed pcm stream */
+int ov_pcm_seek(OggVorbis_File *vf,size64 pos){
+ int i,link=-1;
+ size64 total=ov_pcm_total(vf,-1);
+
+ if(!vf->seekable)return(-1); /* don't dump machine if we can't seek */
+ if(pos<0 || pos>total)goto seek_error;
+
+ /* which bitstream section does this pcm offset occur in? */
+ for(link=vf->links-1;link>=0;link--){
+ total-=vf->pcmlengths[link];
+ if(pos>=total)break;
+ }
+
+ /* seach 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) */
+ {
+ size64 target=pos-total;
+ long end=vf->offsets[link+1];
+ long begin=vf->offsets[link];
+ long best=begin;
+
+ ogg_page og;
+ while(begin<end){
+ long bisect;
+ long ret,acc;
+
+ if(end-begin<CHUNKSIZE){
+ bisect=begin;
+ }else{
+ bisect=(end+begin)/2;
+ }
+
+ _seek_helper(vf,bisect);
+
+ acc=0;
+ while(1){
+ ret=_get_next_page(vf,&og,-1);
+
+ if(ret==-1){
+ end=bisect;
+ }else{
+ size64 frameno=ogg_page_frameno(&og);
+ acc+=ret;
+ if(frameno==-1)continue;
+ if(frameno<target){
+ best=bisect+acc; /* raw offset of packet with frameno */
+ begin=vf->offset; /* raw offset of next packet */
+ }else{
+ end=bisect;
+ }
+ }
+ break;
+ }
+ }
+ /* found our page. seek to it (call raw_seek). */
+
+ if(ov_raw_seek(vf,best))goto seek_error;
+ }
+ /* verify result */
+ if(vf->pcm_offset>=pos)goto seek_error;
+ if(pos>ov_pcm_total(vf,-1))goto seek_error;
+ /* discard samples until we reach the desired position. Crossing a
+ logical bitstream boundary with abandon is OK. */
+ while(vf->pcm_offset<pos){
+ double **pcm;
+ long target=pos-vf->pcm_offset;
+ long samples=vorbis_synthesis_pcmout(&vf->vd,&pcm);
+ if(samples>target)samples=target;
+ vorbis_synthesis_read(&vf->vd,samples);
+ vf->pcm_offset+=samples;
+
+ if(samples<target)
+ if(_process_packet(vf,1)==0)
+ vf->pcm_offset=ov_pcm_total(vf,-1); /* eof */
+ }
+ return 0;
+
+ seek_error:
+ /* dump machine so we're in a known state */
+ _decode_clear(vf);
+ return -1;
}
-/* seek to an offset relative to the decompressed *output* stream */
-int ov_seek_pcm(OggVorbis_File *vf,long pos){
+/* seek to a playback time relative to the decompressed pcm stream */
+int ov_time_seek(OggVorbis_File *vf,double seconds){
+ /* translate time to PCM position and call ov_pcm_seek */
+ int i,link=-1;
+ size64 pcm_total=ov_pcm_total(vf,-1);
+ double time_total=ov_time_total(vf,-1);
+ if(!vf->seekable)return(-1); /* don't dump machine if we can't seek */
+ if(seconds<0 || seconds>time_total)goto seek_error;
+
+ /* which bitstream section does this time offset occur in? */
+ for(link=vf->links-1;link>=0;link--){
+ pcm_total-=vf->pcmlengths[link];
+ time_total-=ov_time_total(vf,link);
+ if(seconds>=time_total)break;
+ }
+
+ /* enough information to convert time offset to pcm offset */
+ {
+ size64 target=pcm_total+(seconds-time_total)*vf->vi[link].rate;
+ return(ov_pcm_seek(vf,target));
+ }
+
+ seek_error:
+ /* dump machine so we're in a known state */
+ _decode_clear(vf);
+ return -1;
+}
+
+/* tell the current stream offset cursor. Note that seek followed by
+ tell will likely not give the set offset due to caching */
+long ov_raw_tell(OggVorbis_File *vf){
+ return(vf->offset);
}
-int ov_seek_time(OggVorbis_File *vf,double seconds){
+size64 ov_pcm_tell(OggVorbis_File *vf){
+ return(vf->pcm_offset);
+}
+double ov_time_tell(OggVorbis_File *vf){
+ /* translate time to PCM position and call ov_pcm_seek */
+ int link=-1;
+ size64 pcm_total=0;
+ double time_total=0.;
+
+ if(vf->seekable){
+ pcm_total=ov_pcm_total(vf,-1);
+ time_total=ov_time_total(vf,-1);
+
+ /* which bitstream section does this time offset occur in? */
+ for(link=vf->links-1;link>=0;link--){
+ pcm_total-=vf->pcmlengths[link];
+ time_total-=ov_time_total(vf,link);
+ if(vf->pcm_offset>pcm_total)break;
+ }
+ }
+ return(time_total+(vf->pcm_offset-pcm_total)/vf->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 */
+
+vorbis_info *ov_info(OggVorbis_File *vf,int link){
+ if(vf->seekable){
+ if(link<0)
+ if(vf->decode_ready)
+ return vf->vi+vf->current_link;
+ else
+ return NULL;
+ else
+ if(link>=vf->links)
+ return NULL;
+ else
+ return vf->vi+link;
+ }else{
+ if(vf->decode_ready)
+ return vf->vi;
+ else
+ return NULL;
+ }
+}
+/* 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.
+
+ ov_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 */
+
+long ov_read(OggVorbis_File *vf,char *buffer,int length,
+ int bigendianp,int word,int sgned,int *bitstream){
+ int i,j;
+
+ while(1){
+ if(vf->decode_ready){
+ double **pcm;
+ long samples=vorbis_synthesis_pcmout(&vf->vd,&pcm);
+ if(samples){
+ /* yay! proceed to pack data into the byte buffer */
+
+ long channels=ov_info(vf,-1)->channels;
+ long bytespersample=word * channels;
+ if(samples>length/bytespersample)samples=length/bytespersample;
+
+ /* a tight loop to pack each size */
+ {
+ if(word==1){
+ int off=(sgned?0:128);
+ for(j=0;j<samples;j++)
+ for(i=0;i<channels;i++){
+ int val=rint(pcm[i][j]*128.);
+ if(val>127)val=127;
+ if(val<-128)val=-128;
+ *buffer++=val+off;
+ }
+ }else{
+ int off=(sgned?0:32768);
+
+ if(bigendianp){
+ for(j=0;j<samples;j++)
+ for(i=0;i<channels;i++){
+ int val=rint(pcm[i][j]*32768.);
+ if(val>32767)val=32767;
+ if(val<-32768)val=-32768;
+ val+=off;
+ *buffer++=(val>>8);
+ *buffer++=(val&0xff);
+ }
+ }else{
+ for(j=0;j<samples;j++)
+ for(i=0;i<channels;i++){
+ int val=rint(pcm[i][j]*32768.);
+ if(val>32767)val=32767;
+ if(val<-32768)val=-32768;
+ val+=off;
+ *buffer++=(val&0xff);
+ *buffer++=(val>>8);
+ }
+
+ }
+ }
+ }
+
+ vorbis_synthesis_read(&vf->vd,samples);
+ vf->pcm_offset+=samples;
+ *bitstream=vf->current_link;
+ return(samples*bytespersample);
+ }
+ }
+
+ /* suck in another packet */
+ switch(_process_packet(vf,1)){
+ case 0:
+ return(0);
+ case -1:
+ return -1;
+ default:
+ break;
+ }
+ }
+}