diff options
| author | Monty <xiphmont@xiph.org> | 1999-11-04 12:37:56 +0000 |
|---|---|---|
| committer | Monty <xiphmont@xiph.org> | 1999-11-04 12:37:56 +0000 |
| commit | 2d7db0df68a2103fa90af66528d03d98ee8a8834 (patch) | |
| tree | cb20e15794c65fa5cf697e44334ad524004c4030 /lib | |
| parent | 8e60b86f874da984edb5fd3d2e7b40ecff8f38e4 (diff) | |
| download | libvorbis-git-2d7db0df68a2103fa90af66528d03d98ee8a8834.tar.gz | |
Full 'vorbisfile.a' library commit. The whole convenience API is
there; the vast majority of the code is untested. chaining_example.c
does work, however.
Monty
svn path=/trunk/vorbis/; revision=160
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/chaining_example.c | 17 | ||||
| -rw-r--r-- | lib/codec.h | 8 | ||||
| -rw-r--r-- | lib/vorbisfile.c | 524 | ||||
| -rw-r--r-- | lib/vorbisfile.h | 32 |
4 files changed, 532 insertions, 49 deletions
diff --git a/lib/chaining_example.c b/lib/chaining_example.c index f686f914..24f9b97d 100644 --- a/lib/chaining_example.c +++ b/lib/chaining_example.c @@ -32,25 +32,24 @@ int main(){ } /* print details about each logical bitstream in the input */ - if(ov.seekable){ + if(ov_seekable(&ov)){ printf("Input bitstream contained %d logical bitstream section(s).\n", - ov.links); + ov_streams(&ov)); printf("Total bitstream playing time: %ld seconds\n\n", - (long)ov_totaltime(&ov)); + (long)ov_time_total(&ov,-1)); }else{ printf("Standard input was not seekable.\n" "First logical bitstream information:\n\n"); } - for(i=0;i<ov.links;i++){ + for(i=0;i<ov_streams(&ov);i++){ + vorbis_info *vi=ov_info(&ov,i); printf("\tlogical bitstream section %d information:\n",i+1); printf("\t\t%ldHz %d channels serial number=%ld\n", - ov.vi[i].rate,ov.vi[i].channels,ov.serialnos[i]); - printf("\t\tcompressed length: %ldbytes ",(ov.offsets? - ov.offsets[i+1]-ov.offsets[i]: - -1)); - printf(" play time: %lds\n",(long)ov_lbtime(&ov,i)); + vi->rate,vi->channels,ov.serialnos[i]); + printf("\t\tcompressed length: %ldbytes ",ov_raw_total(&ov,i)); + printf(" play time: %lds\n",(long)ov_time_total(&ov,i)); } ov_clear(&ov); diff --git a/lib/codec.h b/lib/codec.h index 412c1e17..c6df51a7 100644 --- a/lib/codec.h +++ b/lib/codec.h @@ -172,7 +172,7 @@ typedef struct { int *lacing_vals; /* The values that will go to the segment table */ - int64_t *pcm_vals; /* pcm_pos values for headers. Not compact + size64 *pcm_vals; /* pcm_pos values for headers. Not compact this way, but it is simple coupled to the lacing fifo */ long lacing_storage; @@ -194,7 +194,7 @@ typedef struct { but we need coupling so that the codec (which is in a seperate abstraction layer) also knows about the gap */ - int64_t pcmpos; + size64 pcmpos; } ogg_stream_state; @@ -207,7 +207,7 @@ typedef struct { long b_o_s; long e_o_s; - int64_t frameno; + size64 frameno; long packetno; /* sequence number for decode; the framing knows where there's a hole in the data, but we need coupling so that the codec @@ -346,7 +346,7 @@ extern int ogg_page_version(ogg_page *og); extern int ogg_page_continued(ogg_page *og); extern int ogg_page_bos(ogg_page *og); extern int ogg_page_eos(ogg_page *og); -extern int64_t ogg_page_frameno(ogg_page *og); +extern size64 ogg_page_frameno(ogg_page *og); extern int ogg_page_serialno(ogg_page *og); extern int ogg_page_pageno(ogg_page *og); diff --git a/lib/vorbisfile.c b/lib/vorbisfile.c index cc3e5bdd..59ef54f8 100644 --- a/lib/vorbisfile.c +++ b/lib/vorbisfile.c @@ -14,7 +14,7 @@ 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 ********************************************************************/ @@ -344,22 +344,134 @@ static int _open_seekable(OggVorbis_File *vf){ } 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 */ @@ -418,55 +530,407 @@ int ov_open(FILE *f,OggVorbis_File *vf,char *initial,long ibytes){ 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; + } + } +} diff --git a/lib/vorbisfile.h b/lib/vorbisfile.h index 3bdb14ad..0502de21 100644 --- a/lib/vorbisfile.h +++ b/lib/vorbisfile.h @@ -14,12 +14,12 @@ 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 ********************************************************************/ -#ifndef _VO_FILE_H_ -#define _VO_FILE_H_ +#ifndef _OV_FILE_H_ +#define _OV_FILE_H_ #include <stdio.h> #include "codec.h" @@ -40,7 +40,11 @@ typedef struct { vorbis_info *vi; /* Decoding working state local storage */ - int ready; + size64 pcm_offset; + int decode_ready; + long current_serialno; + int current_link; + ogg_stream_state os; /* take physical pages, weld into a logical stream of packets */ vorbis_dsp_state vd; /* central working state for the packet->PCM decoder */ @@ -51,9 +55,25 @@ typedef struct { extern int ov_clear(OggVorbis_File *vf); extern int ov_open(FILE *f,OggVorbis_File *vf,char *initial,long ibytes); -extern double ov_lbtime(OggVorbis_File *vf,int i); -extern double ov_totaltime(OggVorbis_File *vf); +extern long ov_streams(OggVorbis_File *vf); +extern long ov_seekable(OggVorbis_File *vf); + +extern long ov_raw_total(OggVorbis_File *vf,int i); +extern size64 ov_pcm_total(OggVorbis_File *vf,int i); +extern double ov_time_total(OggVorbis_File *vf,int i); + +extern int ov_raw_seek(OggVorbis_File *vf,long pos); +extern int ov_pcm_seek(OggVorbis_File *vf,size64 pos); +extern int ov_time_seek(OggVorbis_File *vf,double pos); + +extern long ov_raw_tell(OggVorbis_File *vf); +extern size64 ov_pcm_tell(OggVorbis_File *vf); +extern double ov_time_tell(OggVorbis_File *vf); + +extern vorbis_info *ov_info(OggVorbis_File *vf,int link); +extern long ov_read(OggVorbis_File *vf,char *buffer,int length, + int bigendianp,int word,int sgned,int *bitstream); #endif |
