X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=lib%2Fvorbisfile.c;h=831cb39f0bff9c03e2a9d9eb323d39d1b2e2708b;hb=128f0f812b39185d884c67c5f1c780b19aca34ac;hp=7a4094e1b06a42fd63b0130c6f7987778b5387df;hpb=939a038b6faf62b95d599e72564498c71db4b2af;p=platform%2Fupstream%2Flibvorbis.git diff --git a/lib/vorbisfile.c b/lib/vorbisfile.c index 7a4094e..831cb39 100644 --- a/lib/vorbisfile.c +++ b/lib/vorbisfile.c @@ -5,13 +5,12 @@ * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * * * - * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2001 * - * by the XIPHOPHORUS Company http://www.xiph.org/ * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2015 * + * by the Xiph.Org Foundation http://www.xiph.org/ * * * ******************************************************************** function: stdio-based convenience library for opening/seeking/decoding - last mod: $Id: vorbisfile.c,v 1.45 2001/05/27 06:44:01 xiphmont Exp $ ********************************************************************/ @@ -22,6 +21,9 @@ #include #include "vorbis/codec.h" + +/* we don't need or want the static callback symbols here */ +#define OV_EXCLUDE_STATIC_CALLBACKS #include "vorbis/vorbisfile.h" #include "os.h" @@ -48,22 +50,25 @@ information exists in an Ogg bitstream to seek to sample-granularity positions in the output. Or, one can seek by picking some portion of the stream roughly in the desired area if - we only want course navigation through the stream. */ + we only want coarse navigation through the stream. */ /************************************************************************* - * Many, many internal helpers. The intention is not to be confusing; - * rampant duplication and monolithic function implementation would be + * Many, many internal helpers. The intention is not to be confusing; + * rampant duplication and monolithic function implementation would be * harder to understand anyway. The high level functions are last. Begin * grokking near the end of the file */ -/* read a little more data from the file/pipe into the ogg_sync framer */ -#define CHUNKSIZE 8500 /* a shade over 8k; anyone using pages well - over 8k gets what they deserve */ +/* read a little more data from the file/pipe into the ogg_sync framer +*/ +#define CHUNKSIZE 65536 /* greater-than-page-size granularity seeking */ +#define READSIZE 2048 /* a smaller read size is needed for low-rate streaming. */ + static long _get_data(OggVorbis_File *vf){ errno=0; + if(!(vf->callbacks.read_func))return(-1); if(vf->datasource){ - char *buffer=ogg_sync_buffer(&vf->oy,CHUNKSIZE); - long bytes=(vf->callbacks.read_func)(buffer,1,CHUNKSIZE,vf->datasource); + char *buffer=ogg_sync_buffer(&vf->oy,READSIZE); + long bytes=(vf->callbacks.read_func)(buffer,1,READSIZE,vf->datasource); if(bytes>0)ogg_sync_wrote(&vf->oy,bytes); if(bytes==0 && errno)return(-1); return(bytes); @@ -72,15 +77,21 @@ static long _get_data(OggVorbis_File *vf){ } /* save a tiny smidge of verbosity to make the code more readable */ -static void _seek_helper(OggVorbis_File *vf,long offset){ - if(vf->datasource){ - (vf->callbacks.seek_func)(vf->datasource, offset, SEEK_SET); - vf->offset=offset; - ogg_sync_reset(&vf->oy); +static int _seek_helper(OggVorbis_File *vf,ogg_int64_t offset){ + if(vf->datasource){ + /* only seek if the file position isn't already there */ + if(vf->offset != offset){ + if(!(vf->callbacks.seek_func)|| + (vf->callbacks.seek_func)(vf->datasource, offset, SEEK_SET) == -1) + return OV_EREAD; + vf->offset=offset; + ogg_sync_reset(&vf->oy); + } }else{ /* shouldn't happen unless someone writes a broken callback */ - return; + return OV_EFAULT; } + return 0; } /* The read/seek functions track absolute position within the stream */ @@ -91,414 +102,716 @@ static void _seek_helper(OggVorbis_File *vf,long offset){ boundary: -1) unbounded search 0) read no additional data; use cached only - n) search for a new page beginning for n bytes + n) search for a new page beginning for n bytes return: <0) did not find a page (OV_FALSE, OV_EOF, OV_EREAD) n) found a page at absolute offset n */ -static long _get_next_page(OggVorbis_File *vf,ogg_page *og,int boundary){ +static ogg_int64_t _get_next_page(OggVorbis_File *vf,ogg_page *og, + ogg_int64_t boundary){ if(boundary>0)boundary+=vf->offset; while(1){ long more; if(boundary>0 && vf->offset>=boundary)return(OV_FALSE); more=ogg_sync_pageseek(&vf->oy,og); - + if(more<0){ /* skipped n bytes */ vf->offset-=more; }else{ if(more==0){ - /* send more paramedics */ - if(!boundary)return(OV_FALSE); - { - long ret=_get_data(vf); - if(ret==0)return(OV_EOF); - if(ret<0)return(OV_EREAD); - } + /* send more paramedics */ + if(!boundary)return(OV_FALSE); + { + long ret=_get_data(vf); + if(ret==0)return(OV_EOF); + if(ret<0)return(OV_EREAD); + } }else{ - /* got a page. Return the offset at the page beginning, + /* got a page. Return the offset at the page beginning, advance the internal offset past the page end */ - long ret=vf->offset; - vf->offset+=more; - return(ret); - + ogg_int64_t ret=vf->offset; + vf->offset+=more; + return(ret); + } } } } -/* find the latest page beginning before the current stream cursor - position. Much dirtier than the above as Ogg doesn't have any - backward search linkage. no 'readp' as it will certainly have to - read. */ +/* find the latest page beginning before the passed in position. Much + dirtier than the above as Ogg doesn't have any backward search + linkage. no 'readp' as it will certainly have to read. */ /* returns offset or OV_EREAD, OV_FAULT */ -static long _get_prev_page(OggVorbis_File *vf,ogg_page *og){ - long begin=vf->offset; - long ret; - int offset=-1; +static ogg_int64_t _get_prev_page(OggVorbis_File *vf,ogg_int64_t begin,ogg_page *og){ + ogg_int64_t end = begin; + ogg_int64_t ret; + ogg_int64_t offset=-1; while(offset==-1){ begin-=CHUNKSIZE; - _seek_helper(vf,begin); - while(vf->offsetoffset); + if(begin<0) + begin=0; + + ret=_seek_helper(vf,begin); + if(ret)return(ret); + + while(vf->offsetoffset); if(ret==OV_EREAD)return(OV_EREAD); if(ret<0){ - break; + break; }else{ - offset=ret; + offset=ret; } } } - /* we have the offset. Actually snork and hold the page now */ - _seek_helper(vf,offset); - ret=_get_next_page(vf,og,CHUNKSIZE); - if(ret<0) - /* this shouldn't be possible */ - return(OV_EFAULT); + /* In a fully compliant, non-multiplexed stream, we'll still be + holding the last page. In multiplexed (or noncompliant streams), + we will probably have to re-read the last page we saw */ + if(og->header_len==0){ + ret=_seek_helper(vf,offset); + if(ret)return(ret); + + ret=_get_next_page(vf,og,CHUNKSIZE); + if(ret<0) + /* this shouldn't be possible */ + return(OV_EFAULT); + } return(offset); } -/* finds each bitstream link one at a time using a bisection search - (has to begin by knowing the offset of the lb's initial page). - Recurses for each link so it can alloc the link storage after - finding them all, then unroll and fill the cache at the same time */ -static int _bisect_forward_serialno(OggVorbis_File *vf, - long begin, - long searched, - long end, - long currentno, - long m){ - long endsearched=end; - long next=end; - ogg_page og; - long ret; - - /* the below guards against garbage seperating the last and - first pages of two links. */ - while(searched=0)next=ret; - }else{ - searched=ret+og.header_len+og.body_len; +static void _add_serialno(ogg_page *og,long **serialno_list, int *n){ + long s = ogg_page_serialno(og); + (*n)++; + + if(*serialno_list){ + *serialno_list = _ogg_realloc(*serialno_list, sizeof(**serialno_list)*(*n)); + }else{ + *serialno_list = _ogg_malloc(sizeof(**serialno_list)); + } + + (*serialno_list)[(*n)-1] = s; +} + +/* returns nonzero if found */ +static int _lookup_serialno(long s, long *serialno_list, int n){ + if(serialno_list){ + while(n--){ + if(*serialno_list == s) return 1; + serialno_list++; } } + return 0; +} - _seek_helper(vf,next); - ret=_get_next_page(vf,&og,-1); - if(ret==OV_EREAD)return(OV_EREAD); - - if(searched>=end || ret<0){ - vf->links=m+1; - vf->offsets=_ogg_malloc((m+2)*sizeof(ogg_int64_t)); - vf->offsets[m+1]=searched; - }else{ - ret=_bisect_forward_serialno(vf,next,vf->offset, - end,ogg_page_serialno(&og),m+1); - if(ret==OV_EREAD)return(OV_EREAD); +static int _lookup_page_serialno(ogg_page *og, long *serialno_list, int n){ + long s = ogg_page_serialno(og); + return _lookup_serialno(s,serialno_list,n); +} + +/* performs the same search as _get_prev_page, but prefers pages of + the specified serial number. If a page of the specified serialno is + spotted during the seek-back-and-read-forward, it will return the + info of last page of the matching serial number instead of the very + last page. If no page of the specified serialno is seen, it will + return the info of last page and alter *serialno. */ +static ogg_int64_t _get_prev_page_serial(OggVorbis_File *vf, ogg_int64_t begin, + long *serial_list, int serial_n, + int *serialno, ogg_int64_t *granpos){ + ogg_page og; + ogg_int64_t end=begin; + ogg_int64_t ret; + + ogg_int64_t prefoffset=-1; + ogg_int64_t offset=-1; + ogg_int64_t ret_serialno=-1; + ogg_int64_t ret_gran=-1; + + while(offset==-1){ + begin-=CHUNKSIZE; + if(begin<0) + begin=0; + + ret=_seek_helper(vf,begin); + if(ret)return(ret); + + while(vf->offsetoffset); + if(ret==OV_EREAD)return(OV_EREAD); + if(ret<0){ + break; + }else{ + ret_serialno=ogg_page_serialno(&og); + ret_gran=ogg_page_granulepos(&og); + offset=ret; + + if(ret_serialno == *serialno){ + prefoffset=ret; + *granpos=ret_gran; + } + + if(!_lookup_serialno(ret_serialno,serial_list,serial_n)){ + /* we fell off the end of the link, which means we seeked + back too far and shouldn't have been looking in that link + to begin with. If we found the preferred serial number, + forget that we saw it. */ + prefoffset=-1; + } + } + } } - - vf->offsets[m]=begin; - return(0); + + /* we're not interested in the page... just the serialno and granpos. */ + if(prefoffset>=0)return(prefoffset); + + *serialno = ret_serialno; + *granpos = ret_gran; + return(offset); + } /* uses the local ogg_stream storage in vf; this is important for non-streaming input sources */ static int _fetch_headers(OggVorbis_File *vf,vorbis_info *vi,vorbis_comment *vc, - long *serialno,ogg_page *og_ptr){ + long **serialno_list, int *serialno_n, + ogg_page *og_ptr){ ogg_page og; ogg_packet op; - int i,ret=0; - + int i,ret; + int allbos=0; + if(!og_ptr){ - ret=_get_next_page(vf,&og,CHUNKSIZE); - if(ret==OV_EREAD)return(OV_EREAD); - if(ret<0)return OV_ENOTVORBIS; + ogg_int64_t llret=_get_next_page(vf,&og,CHUNKSIZE); + if(llret==OV_EREAD)return(OV_EREAD); + if(llret<0)return(OV_ENOTVORBIS); og_ptr=&og; } - if(serialno)*serialno=ogg_page_serialno(og_ptr); - ogg_stream_init(&vf->os,ogg_page_serialno(og_ptr)); - vf->ready_state=STREAMSET; - - /* extract the initial header from the first page and verify that the - Ogg bitstream is in fact Vorbis data */ - vorbis_info_init(vi); vorbis_comment_init(vc); - - i=0; - while(i<3){ - ogg_stream_pagein(&vf->os,og_ptr); - while(i<3){ - int result=ogg_stream_packetout(&vf->os,&op); - if(result==0)break; - if(result==-1){ - ret=OV_EBADHEADER; - goto bail_header; + vf->ready_state=OPENED; + + /* extract the serialnos of all BOS pages + the first set of vorbis + headers we see in the link */ + + while(ogg_page_bos(og_ptr)){ + if(serialno_list){ + if(_lookup_page_serialno(og_ptr,*serialno_list,*serialno_n)){ + /* a dupe serialnumber in an initial header packet set == invalid stream */ + if(*serialno_list)_ogg_free(*serialno_list); + *serialno_list=0; + *serialno_n=0; + ret=OV_EBADHEADER; + goto bail_header; } - if((ret=vorbis_synthesis_headerin(vi,vc,&op))){ - goto bail_header; + + _add_serialno(og_ptr,serialno_list,serialno_n); + } + + if(vf->ready_stateos,ogg_page_serialno(og_ptr)); + ogg_stream_pagein(&vf->os,og_ptr); + + if(ogg_stream_packetout(&vf->os,&op) > 0 && + vorbis_synthesis_idheader(&op)){ + /* vorbis header; continue setup */ + vf->ready_state=STREAMSET; + if((ret=vorbis_synthesis_headerin(vi,vc,&op))){ + ret=OV_EBADHEADER; + goto bail_header; + } } - i++; } - if(i<3) - if(_get_next_page(vf,og_ptr,1)<0){ - ret=OV_EBADHEADER; - goto bail_header; + + /* get next page */ + { + ogg_int64_t llret=_get_next_page(vf,og_ptr,CHUNKSIZE); + if(llret==OV_EREAD){ + ret=OV_EREAD; + goto bail_header; + } + if(llret<0){ + ret=OV_ENOTVORBIS; + goto bail_header; + } + + /* if this page also belongs to our vorbis stream, submit it and break */ + if(vf->ready_state==STREAMSET && + vf->os.serialno == ogg_page_serialno(og_ptr)){ + ogg_stream_pagein(&vf->os,og_ptr); + break; } + } + } + + if(vf->ready_state!=STREAMSET){ + ret = OV_ENOTVORBIS; + goto bail_header; + } + + while(1){ + + i=0; + while(i<2){ /* get a page loop */ + + while(i<2){ /* get a packet loop */ + + int result=ogg_stream_packetout(&vf->os,&op); + if(result==0)break; + if(result==-1){ + ret=OV_EBADHEADER; + goto bail_header; + } + + if((ret=vorbis_synthesis_headerin(vi,vc,&op))) + goto bail_header; + + i++; + } + + while(i<2){ + if(_get_next_page(vf,og_ptr,CHUNKSIZE)<0){ + ret=OV_EBADHEADER; + goto bail_header; + } + + /* if this page belongs to the correct stream, go parse it */ + if(vf->os.serialno == ogg_page_serialno(og_ptr)){ + ogg_stream_pagein(&vf->os,og_ptr); + break; + } + + /* if we never see the final vorbis headers before the link + ends, abort */ + if(ogg_page_bos(og_ptr)){ + if(allbos){ + ret = OV_EBADHEADER; + goto bail_header; + }else + allbos=1; + } + + /* otherwise, keep looking */ + } + } + + return 0; } - return 0; bail_header: vorbis_info_clear(vi); vorbis_comment_clear(vc); - ogg_stream_clear(&vf->os); + vf->ready_state=OPENED; + return ret; } -/* 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) */ +/* Starting from current cursor position, get initial PCM offset of + next page. Consumes the page in the process without decoding + audio, however this is only called during stream parsing upon + seekable open. */ +static ogg_int64_t _initial_pcmoffset(OggVorbis_File *vf, vorbis_info *vi){ + ogg_page og; + ogg_int64_t accumulated=0; + long lastblock=-1; + int result; + int serialno = vf->os.serialno; + + while(1){ + ogg_packet op; + if(_get_next_page(vf,&og,-1)<0) + break; /* should not be possible unless the file is truncated/mangled */ + + if(ogg_page_bos(&og)) break; + if(ogg_page_serialno(&og)!=serialno) continue; -/* this is void and does not propogate errors up because we want to be - able to open and use damaged bitstreams as well as we can. Just - watch out for missing information for links in the OggVorbis_File - struct */ -static void _prefetch_all_headers(OggVorbis_File *vf, long dataoffset){ + /* count blocksizes of all frames in the page */ + ogg_stream_pagein(&vf->os,&og); + while((result=ogg_stream_packetout(&vf->os,&op))){ + if(result>0){ /* ignore holes */ + long thisblock=vorbis_packet_blocksize(vi,&op); + if(thisblock>=0){ + if(lastblock!=-1) + accumulated+=(lastblock+thisblock)>>2; + lastblock=thisblock; + } + } + } + + if(ogg_page_granulepos(&og)!=-1){ + /* pcm offset of last packet on the first audio page */ + accumulated= ogg_page_granulepos(&og)-accumulated; + break; + } + } + + /* less than zero? Either a corrupt file or a stream with samples + trimmed off the beginning, a normal occurrence; in both cases set + the offset to zero */ + if(accumulated<0)accumulated=0; + + return accumulated; +} + +/* finds each bitstream link one at a time using a bisection search + (has to begin by knowing the offset of the lb's initial page). + Recurses for each link so it can alloc the link storage after + finding them all, then unroll and fill the cache at the same time */ +static int _bisect_forward_serialno(OggVorbis_File *vf, + ogg_int64_t begin, + ogg_int64_t searched, + ogg_int64_t end, + ogg_int64_t endgran, + int endserial, + long *currentno_list, + int currentnos, + long m){ + ogg_int64_t pcmoffset; + ogg_int64_t dataoffset=searched; + ogg_int64_t endsearched=end; + ogg_int64_t next=end; + ogg_int64_t searchgran=-1; ogg_page og; - int i,ret; - - vf->vi=_ogg_realloc(vf->vi,vf->links*sizeof(vorbis_info)); - vf->vc=_ogg_realloc(vf->vc,vf->links*sizeof(vorbis_info)); - vf->dataoffsets=_ogg_malloc(vf->links*sizeof(ogg_int64_t)); - vf->pcmlengths=_ogg_malloc(vf->links*sizeof(ogg_int64_t)); - vf->serialnos=_ogg_malloc(vf->links*sizeof(long)); - - for(i=0;ilinks;i++){ - if(i==0){ - /* we already grabbed the initial header earlier. Just set the offset */ - vf->dataoffsets[i]=dataoffset; - }else{ + ogg_int64_t ret,last; + int serialno = vf->os.serialno; - /* seek to the location of the initial header */ + /* invariants: + we have the headers and serialnos for the link beginning at 'begin' + we have the offset and granpos of the last page in the file (potentially + not a page we care about) + */ + + /* Is the last page in our list of current serialnumbers? */ + if(_lookup_serialno(endserial,currentno_list,currentnos)){ + + /* last page is in the starting serialno list, so we've bisected + down to (or just started with) a single link. Now we need to + find the last vorbis page belonging to the first vorbis stream + for this link. */ + searched = end; + while(endserial != serialno){ + endserial = serialno; + searched=_get_prev_page_serial(vf,searched,currentno_list,currentnos,&endserial,&endgran); + } + + vf->links=m+1; + if(vf->offsets)_ogg_free(vf->offsets); + if(vf->serialnos)_ogg_free(vf->serialnos); + if(vf->dataoffsets)_ogg_free(vf->dataoffsets); + + vf->offsets=_ogg_malloc((vf->links+1)*sizeof(*vf->offsets)); + vf->vi=_ogg_realloc(vf->vi,vf->links*sizeof(*vf->vi)); + vf->vc=_ogg_realloc(vf->vc,vf->links*sizeof(*vf->vc)); + vf->serialnos=_ogg_malloc(vf->links*sizeof(*vf->serialnos)); + vf->dataoffsets=_ogg_malloc(vf->links*sizeof(*vf->dataoffsets)); + vf->pcmlengths=_ogg_malloc(vf->links*2*sizeof(*vf->pcmlengths)); + + vf->offsets[m+1]=end; + vf->offsets[m]=begin; + vf->pcmlengths[m*2+1]=(endgran<0?0:endgran); + + }else{ + + /* last page is not in the starting stream's serial number list, + so we have multiple links. Find where the stream that begins + our bisection ends. */ - _seek_helper(vf,vf->offsets[i]); - if(_fetch_headers(vf,vf->vi+i,vf->vc+i,NULL,NULL)<0){ - vf->dataoffsets[i]=-1; + long *next_serialno_list=NULL; + int next_serialnos=0; + vorbis_info vi; + vorbis_comment vc; + int testserial = serialno+1; + + /* the below guards against garbage seperating the last and + first pages of two links. */ + while(searcheddataoffsets[i]=vf->offset; - ogg_stream_clear(&vf->os); + bisect=(searched+endsearched)/2; } - } - /* get the serial number and PCM length of this link. To do this, - get the last page of the stream */ - { - long end=vf->offsets[i+1]; - _seek_helper(vf,end); + ret=_seek_helper(vf,bisect); + if(ret)return(ret); - while(1){ - ret=_get_prev_page(vf,&og); - if(ret<0){ - /* this should not be possible */ - vorbis_info_clear(vf->vi+i); - vorbis_comment_clear(vf->vc+i); - break; - } - if(ogg_page_granulepos(&og)!=-1){ - vf->serialnos[i]=ogg_page_serialno(&og); - vf->pcmlengths[i]=ogg_page_granulepos(&og); - break; - } - vf->offset=ret; + last=_get_next_page(vf,&og,-1); + if(last==OV_EREAD)return(OV_EREAD); + if(last<0 || !_lookup_page_serialno(&og,currentno_list,currentnos)){ + endsearched=bisect; + if(last>=0)next=last; + }else{ + searched=vf->offset; } } + + /* Bisection point found */ + /* for the time being, fetch end PCM offset the simple way */ + searched = next; + while(testserial != serialno){ + testserial = serialno; + searched = _get_prev_page_serial(vf,searched,currentno_list,currentnos,&testserial,&searchgran); + } + + ret=_seek_helper(vf,next); + if(ret)return(ret); + + ret=_fetch_headers(vf,&vi,&vc,&next_serialno_list,&next_serialnos,NULL); + if(ret)return(ret); + serialno = vf->os.serialno; + dataoffset = vf->offset; + + /* this will consume a page, however the next bisection always + starts with a raw seek */ + pcmoffset = _initial_pcmoffset(vf,&vi); + + ret=_bisect_forward_serialno(vf,next,vf->offset,end,endgran,endserial, + next_serialno_list,next_serialnos,m+1); + if(ret)return(ret); + + if(next_serialno_list)_ogg_free(next_serialno_list); + + vf->offsets[m+1]=next; + vf->serialnos[m+1]=serialno; + vf->dataoffsets[m+1]=dataoffset; + + vf->vi[m+1]=vi; + vf->vc[m+1]=vc; + + vf->pcmlengths[m*2+1]=searchgran; + vf->pcmlengths[m*2+2]=pcmoffset; + vf->pcmlengths[m*2+3]-=pcmoffset; + if(vf->pcmlengths[m*2+3]<0)vf->pcmlengths[m*2+3]=0; } + return(0); } -static void _make_decode_ready(OggVorbis_File *vf){ - if(vf->ready_state!=STREAMSET)return; +static int _make_decode_ready(OggVorbis_File *vf){ + if(vf->ready_state>STREAMSET)return 0; + if(vf->ready_stateseekable){ - vorbis_synthesis_init(&vf->vd,vf->vi+vf->current_link); + if(vorbis_synthesis_init(&vf->vd,vf->vi+vf->current_link)) + return OV_EBADLINK; }else{ - vorbis_synthesis_init(&vf->vd,vf->vi); - } + if(vorbis_synthesis_init(&vf->vd,vf->vi)) + return OV_EBADLINK; + } vorbis_block_init(&vf->vd,&vf->vb); vf->ready_state=INITSET; - return; + vf->bittrack=0.f; + vf->samptrack=0.f; + return 0; } static int _open_seekable2(OggVorbis_File *vf){ - long serialno=vf->current_serialno,end; - long dataoffset=vf->offset; - ogg_page og; + ogg_int64_t dataoffset=vf->dataoffsets[0],end,endgran=-1; + int endserial=vf->os.serialno; + int serialno=vf->os.serialno; /* we're partially open and have a first link header state in storage in vf */ + + /* fetch initial PCM offset */ + ogg_int64_t pcmoffset = _initial_pcmoffset(vf,vf->vi); + /* we can seek, so set out learning all about this file */ - (vf->callbacks.seek_func)(vf->datasource,0,SEEK_END); - vf->offset=vf->end=(vf->callbacks.tell_func)(vf->datasource); - - /* 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(vf,&og); - if(end<0){ - ov_clear(vf); - return(end); + if(vf->callbacks.seek_func && vf->callbacks.tell_func){ + (vf->callbacks.seek_func)(vf->datasource,0,SEEK_END); + vf->offset=vf->end=(vf->callbacks.tell_func)(vf->datasource); + }else{ + vf->offset=vf->end=-1; } - /* more than one logical bitstream? */ - if(ogg_page_serialno(&og)!=serialno){ + /* If seek_func is implemented, tell_func must also be implemented */ + if(vf->end==-1) return(OV_EINVAL); - /* Chained bitstream. Bisect-search each logical bitstream - section. Do so based on serial number only */ - if(_bisect_forward_serialno(vf,0,0,end+1,serialno,0)<0){ - ov_clear(vf); - return(OV_EREAD); - } - - }else{ + /* Get the offset of the last page of the physical bitstream, or, if + we're lucky the last vorbis page of this link as most OggVorbis + files will contain a single logical bitstream */ + end=_get_prev_page_serial(vf,vf->end,vf->serialnos+2,vf->serialnos[1],&endserial,&endgran); + if(end<0)return(end); - /* Only one logical bitstream */ - if(_bisect_forward_serialno(vf,0,end,end+1,serialno,0)){ - ov_clear(vf); - return(OV_EREAD); - } + /* now determine bitstream structure recursively */ + if(_bisect_forward_serialno(vf,0,dataoffset,end,endgran,endserial, + vf->serialnos+2,vf->serialnos[1],0)<0)return(OV_EREAD); - } + vf->offsets[0]=0; + vf->serialnos[0]=serialno; + vf->dataoffsets[0]=dataoffset; + vf->pcmlengths[0]=pcmoffset; + vf->pcmlengths[1]-=pcmoffset; + if(vf->pcmlengths[1]<0)vf->pcmlengths[1]=0; - /* the initial header memory is referenced by vf after; don't free it */ - _prefetch_all_headers(vf,dataoffset); - return(ov_raw_seek(vf,0)); + return(ov_raw_seek(vf,dataoffset)); } -/* clear out the current logical bitstream decoder */ +/* 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->ready_state=OPENED; - - vf->bittrack=0.f; - vf->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). + readp). return: <0) error, OV_HOLE (lost packet) or OV_EOF 0) need more data (only if readp==0) - 1) got a packet + 1) got a packet */ -static int _process_packet(OggVorbis_File *vf,int readp){ +static int _fetch_and_process_packet(OggVorbis_File *vf, + ogg_packet *op_in, + int readp, + int spanp){ 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->ready_state==STREAMSET){ + int ret=_make_decode_ready(vf); + if(ret<0)return ret; + } + + /* process a packet if we can. */ + if(vf->ready_state==INITSET){ - ogg_packet op; - int result=ogg_stream_packetout(&vf->os,&op); - ogg_int64_t granulepos; - - if(result==-1)return(OV_HOLE); /* hole in the data. */ - if(result>0){ - /* got a packet. process it */ - granulepos=op.granulepos; - if(!vorbis_synthesis(&vf->vb,&op)){ /* 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=vorbis_synthesis_pcmout(&vf->vd,NULL); - vorbis_synthesis_blockin(&vf->vd,&vf->vb); - vf->samptrack+=vorbis_synthesis_pcmout(&vf->vd,NULL)-oldsamples; - vf->bittrack+=op.bytes*8; - } - - /* update the pcm offset. */ - if(granulepos!=-1 && !op.e_o_s){ - int link=(vf->seekable?vf->current_link:0); - 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* - - 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=vorbis_synthesis_pcmout(&vf->vd,NULL); - - granulepos-=samples; - for(i=0;ipcmlengths[i]; - vf->pcm_offset=granulepos; - } - return(1); - } + int hs=vorbis_synthesis_halfrate_p(vf->vi); + + while(1) { + ogg_packet op; + ogg_packet *op_ptr=(op_in?op_in:&op); + int result=ogg_stream_packetout(&vf->os,op_ptr); + ogg_int64_t granulepos; + + op_in=NULL; + if(result==-1)return(OV_HOLE); /* hole in the data. */ + if(result>0){ + /* got a packet. process it */ + granulepos=op_ptr->granulepos; + if(!vorbis_synthesis(&vf->vb,op_ptr)){ /* 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=vorbis_synthesis_pcmout(&vf->vd,NULL); + /* for proper use of libvorbis within libvorbisfile, + oldsamples will always be zero. */ + if(oldsamples)return(OV_EFAULT); + + vorbis_synthesis_blockin(&vf->vd,&vf->vb); + vf->samptrack+=(vorbis_synthesis_pcmout(&vf->vd,NULL)<bittrack+=op_ptr->bytes*8; + } + + /* update the pcm offset. */ + if(granulepos!=-1 && !op_ptr->e_o_s){ + int link=(vf->seekable?vf->current_link:0); + 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* + + 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_ptr->e_o_s clause + above */ + + if(vf->seekable && link>0) + granulepos-=vf->pcmlengths[link*2]; + if(granulepos<0)granulepos=0; /* actually, this + shouldn't be possible + here unless the stream + is very broken */ + + samples=(vorbis_synthesis_pcmout(&vf->vd,NULL)<pcmlengths[i*2+1]; + vf->pcm_offset=granulepos; + } + return(1); + } + } + else + break; } } - if(vf->ready_state>=STREAMSET){ - if(!readp)return(0); - if(_get_next_page(vf,&og,-1)<0)return(OV_EOF); /* eof. - leave unitialized */ - - /* bitrate tracking; add the header's bytes here, the body bytes - are done by packet above */ - vf->bittrack+=og.header_len*8; - - /* has our decoding just traversed a bitstream boundary? */ - if(vf->ready_state==INITSET){ - if(vf->current_serialno!=ogg_page_serialno(&og)){ - _decode_clear(vf); - - if(!vf->seekable){ - vorbis_info_clear(vf->vi); - vorbis_comment_clear(vf->vc); - } - } + if(vf->ready_state>=OPENED){ + ogg_int64_t ret; + + while(1){ + /* the loop is not strictly necessary, but there's no sense in + doing the extra checks of the larger loop for the common + case in a multiplexed bistream where the page is simply + part of a different logical bitstream; keep reading until + we get one with the correct serialno */ + + if(!readp)return(0); + if((ret=_get_next_page(vf,&og,-1))<0){ + return(OV_EOF); /* eof. leave unitialized */ + } + + /* bitrate tracking; add the header's bytes here, the body bytes + are done by packet above */ + vf->bittrack+=og.header_len*8; + + if(vf->ready_state==INITSET){ + if(vf->current_serialno!=ogg_page_serialno(&og)){ + + /* two possibilities: + 1) our decoding just traversed a bitstream boundary + 2) another stream is multiplexed into this logical section */ + + if(ogg_page_bos(&og)){ + /* boundary case */ + if(!spanp) + return(OV_EOF); + + _decode_clear(vf); + + if(!vf->seekable){ + vorbis_info_clear(vf->vi); + vorbis_comment_clear(vf->vc); + } + break; + + }else + continue; /* possibility #2 */ + } + } + + break; } } /* Do we need to load a new machine before submitting the page? */ - /* This is different in the seekable and non-seekable cases. + /* 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 @@ -509,56 +822,65 @@ static int _process_packet(OggVorbis_File *vf,int readp){ we're now nominally at the header of the next bitstream */ - if(vf->ready_state!=INITSET){ + if(vf->ready_state!=INITSET){ int link; if(vf->ready_stateseekable){ - 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;linklinks;link++) - if(vf->serialnos[link]==vf->current_serialno)break; - if(link==vf->links)return(OV_EBADLINK); /* sign of a bogus - stream. error out, - leave machine - uninitialized */ - - vf->current_link=link; - - ogg_stream_init(&vf->os,vf->current_serialno); - ogg_stream_reset(&vf->os); - vf->ready_state=STREAMSET; - - }else{ - /* we're streaming */ - /* fetch the three header packets, build the info struct */ - - _fetch_headers(vf,vf->vi,vf->vc,&vf->current_serialno,&og); - vf->current_link++; - link=0; - } + if(vf->seekable){ + long 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;linklinks;link++) + if(vf->serialnos[link]==serialno)break; + + if(link==vf->links) continue; /* not the desired Vorbis + bitstream section; keep + trying */ + + vf->current_serialno=serialno; + vf->current_link=link; + + ogg_stream_reset_serialno(&vf->os,vf->current_serialno); + vf->ready_state=STREAMSET; + + }else{ + /* we're streaming */ + /* fetch the three header packets, build the info struct */ + + int ret=_fetch_headers(vf,vf->vi,vf->vc,NULL,NULL,&og); + if(ret)return(ret); + vf->current_serialno=vf->os.serialno; + vf->current_link++; + link=0; + } } - - _make_decode_ready(vf); } + + /* the buffered page is the data we want, and we're ready for it; + add it to the stream state */ ogg_stream_pagein(&vf->os,&og); + } } +/* if, eg, 64 bit stdio is configured by default, this will build with + fseek64 */ static int _fseek64_wrap(FILE *f,ogg_int64_t off,int whence){ if(f==NULL)return(-1); - return fseek(f,(int)off,whence); + return fseek(f,off,whence); } -static int _ov_open1(void *f,OggVorbis_File *vf,char *initial, - long ibytes, ov_callbacks callbacks){ - long offset=(f?callbacks.seek_func(f,0,SEEK_CUR):-1); +static int _ov_open1(void *f,OggVorbis_File *vf,const char *initial, + long ibytes, ov_callbacks callbacks){ + int offsettest=((f && callbacks.seek_func)?callbacks.seek_func(f,0,SEEK_CUR):-1); + long *serialno_list=NULL; + int serialno_list_size=0; int ret; - memset(vf,0,sizeof(OggVorbis_File)); + memset(vf,0,sizeof(*vf)); vf->datasource=f; vf->callbacks = callbacks; @@ -567,8 +889,8 @@ static int _ov_open1(void *f,OggVorbis_File *vf,char *initial, /* 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) */ + previously read data (especially as we may be reading from a + non-seekable stream) */ if(initial){ char *buffer=ogg_sync_buffer(&vf->oy,ibytes); memcpy(buffer,initial,ibytes); @@ -576,24 +898,42 @@ static int _ov_open1(void *f,OggVorbis_File *vf,char *initial, } /* can we seek? Stevens suggests the seek test was portable */ - if(offset!=-1)vf->seekable=1; + if(offsettest!=-1)vf->seekable=1; /* No seeking yet; Set up a 'single' (current) logical bitstream entry for partial open */ vf->links=1; - vf->vi=_ogg_calloc(vf->links,sizeof(vorbis_info)); - vf->vc=_ogg_calloc(vf->links,sizeof(vorbis_info)); - - /* Try to fetch the headers, maintaining all the storage */ - if((ret=_fetch_headers(vf,vf->vi,vf->vc,&vf->current_serialno,NULL))<0){ + vf->vi=_ogg_calloc(vf->links,sizeof(*vf->vi)); + vf->vc=_ogg_calloc(vf->links,sizeof(*vf->vc)); + ogg_stream_init(&vf->os,-1); /* fill in the serialno later */ + + /* Fetch all BOS pages, store the vorbis header and all seen serial + numbers, load subsequent vorbis setup headers */ + if((ret=_fetch_headers(vf,vf->vi,vf->vc,&serialno_list,&serialno_list_size,NULL))<0){ vf->datasource=NULL; ov_clear(vf); - }else + }else{ + /* serial number list for first link needs to be held somewhere + for second stage of seekable stream open; this saves having to + seek/reread first link's serialnumber data then. */ + vf->serialnos=_ogg_calloc(serialno_list_size+2,sizeof(*vf->serialnos)); + vf->serialnos[0]=vf->current_serialno=vf->os.serialno; + vf->serialnos[1]=serialno_list_size; + memcpy(vf->serialnos+2,serialno_list,serialno_list_size*sizeof(*vf->serialnos)); + + vf->offsets=_ogg_calloc(1,sizeof(*vf->offsets)); + vf->dataoffsets=_ogg_calloc(1,sizeof(*vf->dataoffsets)); + vf->offsets[0]=0; + vf->dataoffsets[0]=vf->offset; + vf->ready_state=PARTOPEN; + } + if(serialno_list)_ogg_free(serialno_list); return(ret); } static int _ov_open2(OggVorbis_File *vf){ + if(vf->ready_state != PARTOPEN) return OV_EINVAL; vf->ready_state=OPENED; if(vf->seekable){ int ret=_open_seekable2(vf); @@ -602,7 +942,9 @@ static int _ov_open2(OggVorbis_File *vf){ ov_clear(vf); } return(ret); - } + }else + vf->ready_state=STREAMSET; + return 0; } @@ -613,12 +955,12 @@ int ov_clear(OggVorbis_File *vf){ vorbis_block_clear(&vf->vb); vorbis_dsp_clear(&vf->vd); ogg_stream_clear(&vf->os); - + if(vf->vi && vf->links){ int i; for(i=0;ilinks;i++){ - vorbis_info_clear(vf->vi+i); - vorbis_comment_clear(vf->vc+i); + vorbis_info_clear(vf->vi+i); + vorbis_comment_clear(vf->vc+i); } _ogg_free(vf->vi); _ogg_free(vf->vc); @@ -628,8 +970,9 @@ int ov_clear(OggVorbis_File *vf){ if(vf->serialnos)_ogg_free(vf->serialnos); if(vf->offsets)_ogg_free(vf->offsets); ogg_sync_clear(&vf->oy); - if(vf->datasource)(vf->callbacks.close_func)(vf->datasource); - memset(vf,0,sizeof(OggVorbis_File)); + if(vf->datasource && vf->callbacks.close_func) + (vf->callbacks.close_func)(vf->datasource); + memset(vf,0,sizeof(*vf)); } #ifdef DEBUG_LEAKS _VDBG_dump(); @@ -639,20 +982,20 @@ int ov_clear(OggVorbis_File *vf){ /* 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. + bitstream sections that are truncated/woogie. return: -1) error 0) OK */ -int ov_open_callbacks(void *f,OggVorbis_File *vf,char *initial,long ibytes, - ov_callbacks callbacks){ +int ov_open_callbacks(void *f,OggVorbis_File *vf, + const char *initial,long ibytes,ov_callbacks callbacks){ int ret=_ov_open1(f,vf,initial,ibytes,callbacks); if(ret)return ret; return _ov_open2(vf); } -int ov_open(FILE *f,OggVorbis_File *vf,char *initial,long ibytes){ +int ov_open(FILE *f,OggVorbis_File *vf,const char *initial,long ibytes){ ov_callbacks callbacks = { (size_t (*)(void *, size_t, size_t, void *)) fread, (int (*)(void *, ogg_int64_t, int)) _fseek64_wrap, @@ -662,19 +1005,67 @@ int ov_open(FILE *f,OggVorbis_File *vf,char *initial,long ibytes){ return ov_open_callbacks((void *)f, vf, initial, ibytes, callbacks); } - + +int ov_fopen(const char *path,OggVorbis_File *vf){ + int ret; + FILE *f = fopen(path,"rb"); + if(!f) return -1; + + ret = ov_open(f,vf,NULL,0); + if(ret) fclose(f); + return ret; +} + + +/* cheap hack for game usage where downsampling is desirable; there's + no need for SRC as we can just do it cheaply in libvorbis. */ + +int ov_halfrate(OggVorbis_File *vf,int flag){ + int i; + if(vf->vi==NULL)return OV_EINVAL; + if(vf->ready_state>STREAMSET){ + /* clear out stream state; dumping the decode machine is needed to + reinit the MDCT lookups. */ + vorbis_dsp_clear(&vf->vd); + vorbis_block_clear(&vf->vb); + vf->ready_state=STREAMSET; + if(vf->pcm_offset>=0){ + ogg_int64_t pos=vf->pcm_offset; + vf->pcm_offset=-1; /* make sure the pos is dumped if unseekable */ + ov_pcm_seek(vf,pos); + } + } + + for(i=0;ilinks;i++){ + if(vorbis_synthesis_halfrate(vf->vi+i,flag)){ + if(flag) ov_halfrate(vf,0); + return OV_EINVAL; + } + } + return 0; +} + +int ov_halfrate_p(OggVorbis_File *vf){ + if(vf->vi==NULL)return OV_EINVAL; + return vorbis_synthesis_halfrate_p(vf->vi); +} + /* Only partially open the vorbis file; test for Vorbisness, and load the headers for the first chain. Do not seek (although test for seekability). Use ov_test_open to finish opening the file, else - ov_clear to close/free it. Same return codes as open. */ + ov_clear to close/free it. Same return codes as open. -int ov_test_callbacks(void *f,OggVorbis_File *vf,char *initial,long ibytes, - ov_callbacks callbacks) + Note that vorbisfile does _not_ take ownership of the file if the + call fails; the calling applicaiton is responsible for closing the file + if this call returns an error. */ + +int ov_test_callbacks(void *f,OggVorbis_File *vf, + const char *initial,long ibytes,ov_callbacks callbacks) { return _ov_open1(f,vf,initial,ibytes,callbacks); } -int ov_test(FILE *f,OggVorbis_File *vf,char *initial,long ibytes){ +int ov_test(FILE *f,OggVorbis_File *vf,const char *initial,long ibytes){ ov_callbacks callbacks = { (size_t (*)(void *, size_t, size_t, void *)) fread, (int (*)(void *, ogg_int64_t, int)) _fseek64_wrap, @@ -684,7 +1075,7 @@ int ov_test(FILE *f,OggVorbis_File *vf,char *initial,long ibytes){ return ov_test_callbacks((void *)f, vf, initial, ibytes, callbacks); } - + int ov_test_open(OggVorbis_File *vf){ if(vf->ready_state!=PARTOPEN)return(OV_EINVAL); return _ov_open2(vf); @@ -716,9 +1107,15 @@ long ov_bitrate(OggVorbis_File *vf,int i){ if(i<0){ ogg_int64_t bits=0; int i; + float br; for(i=0;ilinks;i++) bits+=(vf->offsets[i+1]-vf->dataoffsets[i])*8; - return(rint(bits/ov_time_total(vf,-1))); + /* This once read: return(rint(bits/ov_time_total(vf,-1))); + * gcc 3.x on x86 miscompiled this at optimisation level 2 and above, + * so this is slightly transformed to make it work. + */ + br = bits/ov_time_total(vf,-1); + return(rint(br)); }else{ if(vf->seekable){ /* return the actual bitrate */ @@ -726,16 +1123,16 @@ long ov_bitrate(OggVorbis_File *vf,int i){ }else{ /* return nominal if set */ if(vf->vi[i].bitrate_nominal>0){ - return vf->vi[i].bitrate_nominal; + return vf->vi[i].bitrate_nominal; }else{ - if(vf->vi[i].bitrate_upper>0){ - if(vf->vi[i].bitrate_lower>0){ - return (vf->vi[i].bitrate_upper+vf->vi[i].bitrate_lower)/2; - }else{ - return vf->vi[i].bitrate_upper; - } - } - return(OV_FALSE); + if(vf->vi[i].bitrate_upper>0){ + if(vf->vi[i].bitrate_lower>0){ + return (vf->vi[i].bitrate_upper+vf->vi[i].bitrate_lower)/2; + }else{ + return vf->vi[i].bitrate_upper; + } + } + return(OV_FALSE); } } } @@ -743,7 +1140,7 @@ long ov_bitrate(OggVorbis_File *vf,int i){ /* returns the actual bitrate since last call. returns -1 if no additional data to offer since last call (or at beginning of stream), - EINVAL if stream is only partially open + EINVAL if stream is only partially open */ long ov_bitrate_instant(OggVorbis_File *vf){ int link=(vf->seekable?vf->current_link:0); @@ -769,14 +1166,14 @@ long ov_serialnumber(OggVorbis_File *vf,int i){ /* returns: total raw (compressed) length of content if i==-1 raw (compressed) length of that logical bitstream for i==0 to n - OV_EINVAL if the stream is not seekable (we can't know the length) - or if stream is only partially open + OV_EINVAL if the stream is not seekable (we can't know the length) + or if stream is only partially open */ ogg_int64_t ov_raw_total(OggVorbis_File *vf,int i){ if(vf->ready_stateseekable || i>=vf->links)return(OV_EINVAL); if(i<0){ - long acc=0; + ogg_int64_t acc=0; int i; for(i=0;ilinks;i++) acc+=ov_raw_total(vf,i); @@ -787,9 +1184,9 @@ ogg_int64_t ov_raw_total(OggVorbis_File *vf,int i){ } /* returns: total PCM length (samples) of content if i==-1 PCM length - (samples) of that logical bitstream for i==0 to n - OV_EINVAL if the stream is not seekable (we can't know the - length) or only partially open + (samples) of that logical bitstream for i==0 to n + OV_EINVAL if the stream is not seekable (we can't know the + length) or only partially open */ ogg_int64_t ov_pcm_total(OggVorbis_File *vf,int i){ if(vf->ready_statepcmlengths[i]); + return(vf->pcmlengths[i*2+1]); } } /* returns: total seconds of content if i==-1 seconds in that logical bitstream for i==0 to n - OV_EINVAL if the stream is not seekable (we can't know the - length) or only partially open + OV_EINVAL if the stream is not seekable (we can't know the + length) or only partially open */ double ov_time_total(OggVorbis_File *vf,int i){ if(vf->ready_statepcmlengths[i])/vf->vi[i].rate); + return((double)(vf->pcmlengths[i*2+1])/vf->vi[i].rate); } } @@ -831,20 +1228,36 @@ double ov_time_total(OggVorbis_File *vf,int i){ returns zero on success, nonzero on failure */ -int ov_raw_seek(OggVorbis_File *vf,long pos){ +int ov_raw_seek(OggVorbis_File *vf,ogg_int64_t pos){ ogg_stream_state work_os; if(vf->ready_stateseekable) return(OV_ENOSEEK); /* don't dump machine if we can't seek */ - if(pos<0 || pos>vf->offsets[vf->links])return(OV_EINVAL); + if(pos<0 || pos>vf->end)return(OV_EINVAL); - /* clear out decoding machine state */ + /* is the seek position outside our current link [if any]? */ + if(vf->ready_state>=STREAMSET){ + if(posoffsets[vf->current_link] || pos>=vf->offsets[vf->current_link+1]) + _decode_clear(vf); /* clear out stream state */ + } + + /* don't yet clear out decoding machine (if it's initialized), in + the case we're in the same link. Restart the decode lapping, and + let _fetch_and_process_packet deal with a potential bitstream + boundary */ vf->pcm_offset=-1; - _decode_clear(vf); - - _seek_helper(vf,pos); + ogg_stream_reset_serialno(&vf->os, + vf->current_serialno); /* must set serialno */ + vorbis_synthesis_restart(&vf->vd); + + if(_seek_helper(vf,pos)) { + /* dump the machine so we're in a known state */ + vf->pcm_offset=-1; + _decode_clear(vf); + return OV_EBADLINK; + } /* we need to make sure the pcm_offset is set, but we don't want to advance the raw cursor past good packets just to get to the first @@ -852,13 +1265,13 @@ int ov_raw_seek(OggVorbis_File *vf,long pos){ decoding as immediately after the seek position as possible. So, a hack. We use two stream states; a local scratch state and - a the shared vf->os stream state. We use the local state to - scan, and the shared state as a buffer for later decode. + the shared vf->os stream state. We use the local state to + scan, and the shared state as a buffer for later decode. Unfortuantely, on the last page we still advance to last packet because the granulepos on the last page is not necessarily on a packet boundary, and we need to make sure the granpos is - correct. + correct. */ { @@ -866,259 +1279,406 @@ int ov_raw_seek(OggVorbis_File *vf,long pos){ ogg_packet op; int lastblock=0; int accblock=0; - int thisblock; - int eosflag; + int thisblock=0; + int lastflag=0; + int firstflag=0; + ogg_int64_t pagepos=-1; - memset(&work_os,0,sizeof(work_os));/* so that it's safe to clear - it later even if we don't - init it */ + ogg_stream_init(&work_os,vf->current_serialno); /* get the memory ready */ + ogg_stream_reset(&work_os); /* eliminate the spurious OV_HOLE + return from not necessarily + starting from the beginning */ while(1){ - if(vf->ready_state==STREAMSET){ - /* snarf/scan a packet if we can */ - int result=ogg_stream_packetout(&work_os,&op); - - if(result>0){ - - if(vf->vi[vf->current_link].codec_setup) - thisblock=vorbis_packet_blocksize(vf->vi+vf->current_link,&op); - if(eosflag) - ogg_stream_packetout(&vf->os,NULL); - else - if(lastblock)accblock+=(lastblock+thisblock)>>2; - - if(op.granulepos!=-1){ - int i,link=vf->current_link; - ogg_int64_t granulepos=op.granulepos; - - for(i=0;ipcmlengths[i]; - vf->pcm_offset=granulepos-accblock; - break; - } - lastblock=thisblock; - continue; - } + if(vf->ready_state>=STREAMSET){ + /* snarf/scan a packet if we can */ + int result=ogg_stream_packetout(&work_os,&op); + + if(result>0){ + + if(vf->vi[vf->current_link].codec_setup){ + thisblock=vorbis_packet_blocksize(vf->vi+vf->current_link,&op); + if(thisblock<0){ + ogg_stream_packetout(&vf->os,NULL); + thisblock=0; + }else{ + + /* We can't get a guaranteed correct pcm position out of the + last page in a stream because it might have a 'short' + granpos, which can only be detected in the presence of a + preceding page. However, if the last page is also the first + page, the granpos rules of a first page take precedence. Not + only that, but for first==last, the EOS page must be treated + as if its a normal first page for the stream to open/play. */ + if(lastflag && !firstflag) + ogg_stream_packetout(&vf->os,NULL); + else + if(lastblock)accblock+=(lastblock+thisblock)>>2; + } + + if(op.granulepos!=-1){ + int i,link=vf->current_link; + ogg_int64_t granulepos=op.granulepos-vf->pcmlengths[link*2]; + if(granulepos<0)granulepos=0; + + for(i=0;ipcmlengths[i*2+1]; + vf->pcm_offset=granulepos-accblock; + if(vf->pcm_offset<0)vf->pcm_offset=0; + break; + } + lastblock=thisblock; + continue; + }else + ogg_stream_packetout(&vf->os,NULL); + } } - + if(!lastblock){ - if(_get_next_page(vf,&og,-1)<0){ - vf->pcm_offset=ov_pcm_total(vf,-1); - break; - } + pagepos=_get_next_page(vf,&og,-1); + if(pagepos<0){ + vf->pcm_offset=ov_pcm_total(vf,-1); + break; + } }else{ - /* huh? Bogus stream with packets but no granulepos */ - vf->pcm_offset=-1; - break; + /* huh? Bogus stream with packets but no granulepos */ + vf->pcm_offset=-1; + break; } - + /* has our decoding just traversed a bitstream boundary? */ - if(vf->ready_state==STREAMSET) - if(vf->current_serialno!=ogg_page_serialno(&og)){ - _decode_clear(vf); /* clear out stream state */ - ogg_stream_clear(&work_os); + if(vf->ready_state>=STREAMSET){ + if(vf->current_serialno!=ogg_page_serialno(&og)){ + + /* two possibilities: + 1) our decoding just traversed a bitstream boundary + 2) another stream is multiplexed into this logical section? */ + + if(ogg_page_bos(&og)){ + /* we traversed */ + _decode_clear(vf); /* clear out stream state */ + ogg_stream_clear(&work_os); + } /* else, do nothing; next loop will scoop another page */ + } } if(vf->ready_statecurrent_serialno=ogg_page_serialno(&og); - for(link=0;linklinks;link++) - if(vf->serialnos[link]==vf->current_serialno)break; - if(link==vf->links)goto seek_error; /* sign of a bogus stream. - error out, leave - machine uninitialized */ - vf->current_link=link; - - ogg_stream_init(&vf->os,vf->current_serialno); - ogg_stream_reset(&vf->os); - ogg_stream_init(&work_os,vf->current_serialno); - ogg_stream_reset(&work_os); - vf->ready_state=STREAMSET; - + int link; + long serialno = ogg_page_serialno(&og); + + for(link=0;linklinks;link++) + if(vf->serialnos[link]==serialno)break; + + if(link==vf->links) continue; /* not the desired Vorbis + bitstream section; keep + trying */ + vf->current_link=link; + vf->current_serialno=serialno; + ogg_stream_reset_serialno(&vf->os,serialno); + ogg_stream_reset_serialno(&work_os,serialno); + vf->ready_state=STREAMSET; + firstflag=(pagepos<=vf->dataoffsets[link]); } - + ogg_stream_pagein(&vf->os,&og); ogg_stream_pagein(&work_os,&og); - eosflag=ogg_page_eos(&og); + lastflag=ogg_page_eos(&og); + } } ogg_stream_clear(&work_os); + vf->bittrack=0.f; + vf->samptrack=0.f; return(0); - - seek_error: - /* dump the machine so we're in a known state */ - vf->pcm_offset=-1; - ogg_stream_clear(&work_os); - _decode_clear(vf); - return OV_EBADLINK; } /* Page granularity seek (faster than sample granularity because we don't do the last bit of decode to find a specific sample). - Seek to the last [granule marked] page preceeding the specified pos + Seek to the last [granule marked] page preceding the specified pos location, such that decoding past the returned point will quickly arrive at the requested position. */ int ov_pcm_seek_page(OggVorbis_File *vf,ogg_int64_t pos){ int link=-1; - long ret; + ogg_int64_t result=0; ogg_int64_t total=ov_pcm_total(vf,-1); if(vf->ready_stateseekable)return(OV_ENOSEEK); + if(pos<0 || pos>total)return(OV_EINVAL); - + /* which bitstream section does this pcm offset occur in? */ for(link=vf->links-1;link>=0;link--){ - total-=vf->pcmlengths[link]; + total-=vf->pcmlengths[link*2+1]; 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) */ + /* Search within the logical bitstream for the page with the highest + pcm_pos preceding pos. If we're looking for a position on the + first page, bisection will halt without finding our position as + it's before the first explicit granulepos fencepost. That case is + handled separately below. + + 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) */ + + /* new search algorithm originally by HB (Nicholas Vinen) */ - /* new search algorithm by HB (Nicholas Vinen) */ { - ogg_int64_t target=pos-total; - long end=vf->offsets[link+1]; - long begin=vf->offsets[link]; - ogg_int64_t endtime = vf->pcmlengths[link]; - ogg_int64_t begintime = 0; - long best=begin; - + ogg_int64_t end=vf->offsets[link+1]; + ogg_int64_t begin=vf->dataoffsets[link]; + ogg_int64_t begintime = vf->pcmlengths[link*2]; + ogg_int64_t endtime = vf->pcmlengths[link*2+1]+begintime; + ogg_int64_t target=pos-total+begintime; + ogg_int64_t best=-1; + int got_page=0; + ogg_page og; + + /* if we have only one page, there will be no bisection. Grab the page here */ + if(begin==end){ + result=_seek_helper(vf,begin); + if(result) goto seek_error; + + result=_get_next_page(vf,&og,1); + if(result<0) goto seek_error; + + got_page=1; + } + + /* bisection loop */ while(beginoffset; /* raw offset of next page */ - begintime=granulepos; - - if(target-begin>44100)break; - bisect=begin; /* *not* begin + 1 */ - }else{ - if(bisect<=begin+1) - end=begin; /* found it */ - else{ - if(end==vf->offset){ /* we're pretty close - we'd be stuck in */ - end=ret; - bisect-=CHUNKSIZE; /* an endless loop otherwise. */ - if(bisect<=begin)bisect=begin+1; - _seek_helper(vf,bisect); - }else{ - end=ret; - endtime=granulepos; - break; - } - } - } - } + result=_get_next_page(vf,&og,end-vf->offset); + if(result==OV_EREAD) goto seek_error; + if(result<0){ + /* there is no next page! */ + if(bisect<=begin+1) + /* No bisection left to perform. We've either found the + best candidate already or failed. Exit loop. */ + end=begin; + else{ + /* We tried to load a fraction of the last page; back up a + bit and try to get the whole last page */ + if(bisect==0) goto seek_error; + bisect-=CHUNKSIZE; + + /* don't repeat/loop on a read we've already performed */ + if(bisect<=begin)bisect=begin+1; + + /* seek and cntinue bisection */ + result=_seek_helper(vf,bisect); + if(result) goto seek_error; + } + }else{ + ogg_int64_t granulepos; + got_page=1; + + /* got a page. analyze it */ + /* only consider pages from primary vorbis stream */ + if(ogg_page_serialno(&og)!=vf->serialnos[link]) + continue; + + /* only consider pages with the granulepos set */ + granulepos=ogg_page_granulepos(&og); + if(granulepos==-1)continue; + + if(granuleposoffset; /* raw offset of next page */ + begintime=granulepos; + + /* if we're before our target but within a short distance, + don't bisect; read forward */ + if(target-begintime>44100)break; + + bisect=begin; /* *not* begin + 1 as above */ + }else{ + + /* This is one of our pages, but the granpos is + post-target; it is not a bisection return + candidate. (The only way we'd use it is if it's the + first page in the stream; we handle that case later + outside the bisection) */ + if(bisect<=begin+1){ + /* No bisection left to perform. We've either found the + best candidate already or failed. Exit loop. */ + end=begin; + }else{ + if(end==vf->offset){ + /* bisection read to the end; use the known page + boundary (result) to update bisection, back up a + little bit, and try again */ + end=result; + bisect-=CHUNKSIZE; + if(bisect<=begin)bisect=begin+1; + result=_seek_helper(vf,bisect); + if(result) goto seek_error; + }else{ + /* Normal bisection */ + end=bisect; + endtime=granulepos; + break; + } + } + } + } } } - /* found our page. seek to it, update pcm offset. Easier case than - raw_seek, don't keep packets preceeding granulepos. */ - { + /* Out of bisection: did it 'fail?' */ + if(best == -1){ + + /* Check the 'looking for data in first page' special case; + bisection would 'fail' because our search target was before the + first PCM granule position fencepost. */ + + if(got_page && + begin == vf->dataoffsets[link] && + ogg_page_serialno(&og)==vf->serialnos[link]){ + + /* Yes, this is the beginning-of-stream case. We already have + our page, right at the beginning of PCM data. Set state + and return. */ + + vf->pcm_offset=total; + + if(link!=vf->current_link){ + /* Different link; dump entire decode machine */ + _decode_clear(vf); + + vf->current_link=link; + vf->current_serialno=vf->serialnos[link]; + vf->ready_state=STREAMSET; + + }else{ + vorbis_synthesis_restart(&vf->vd); + } + + ogg_stream_reset_serialno(&vf->os,vf->current_serialno); + ogg_stream_pagein(&vf->os,&og); + + }else + goto seek_error; + + }else{ + + /* Bisection found our page. seek to it, update pcm offset. Easier case than + raw_seek, don't keep packets preceding granulepos. */ + ogg_page og; ogg_packet op; - /* clear out decoding machine state */ - _decode_clear(vf); + /* seek */ - _seek_helper(vf,best); - - if(_get_next_page(vf,&og,-1)<0)return(OV_EOF); /* shouldn't happen */ - vf->current_serialno=ogg_page_serialno(&og); - vf->current_link=link; - - ogg_stream_init(&vf->os,vf->current_serialno); - ogg_stream_reset(&vf->os); - vf->ready_state=STREAMSET; + result=_seek_helper(vf,best); + vf->pcm_offset=-1; + if(result) goto seek_error; + result=_get_next_page(vf,&og,-1); + if(result<0) goto seek_error; + + if(link!=vf->current_link){ + /* Different link; dump entire decode machine */ + _decode_clear(vf); + + vf->current_link=link; + vf->current_serialno=vf->serialnos[link]; + vf->ready_state=STREAMSET; + + }else{ + vorbis_synthesis_restart(&vf->vd); + } + + ogg_stream_reset_serialno(&vf->os,vf->current_serialno); ogg_stream_pagein(&vf->os,&og); /* pull out all but last packet; the one with granulepos */ while(1){ - ret=ogg_stream_packetpeek(&vf->os,&op); - if(ret==0){ - /* !!! the packet finishing this page originated on a - preceeding page. Keep fetching previous pages until we - get one with a granulepos or without the 'continued' flag - set. Then just use raw_seek for simplicity. */ - while(1){ - ret=_get_prev_page(vf,&og); - if(ret<0)goto seek_error; - if(ogg_page_granulepos(&og)>-1 || - !ogg_page_continued(&og)){ - return ov_raw_seek(vf,ret); - } - vf->offset=ret; - } - } - if(ret<0)goto seek_error; - if(op.granulepos!=-1 && !op.e_o_s){ - vf->pcm_offset=op.granulepos+total; - break; - }else - ret=ogg_stream_packetout(&vf->os,NULL); + result=ogg_stream_packetpeek(&vf->os,&op); + if(result==0){ + /* No packet returned; we exited the bisection with 'best' + pointing to a page with a granule position, so the packet + finishing this page ('best') originated on a preceding + page. Keep fetching previous pages until we get one with + a granulepos or without the 'continued' flag set. Then + just use raw_seek for simplicity. */ + /* Do not rewind past the beginning of link data; if we do, + it's either a bug or a broken stream */ + result=best; + while(result>vf->dataoffsets[link]){ + result=_get_prev_page(vf,result,&og); + if(result<0) goto seek_error; + if(ogg_page_serialno(&og)==vf->current_serialno && + (ogg_page_granulepos(&og)>-1 || + !ogg_page_continued(&og))){ + return ov_raw_seek(vf,result); + } + } + } + if(result<0){ + result = OV_EBADPACKET; + goto seek_error; + } + if(op.granulepos!=-1){ + vf->pcm_offset=op.granulepos-vf->pcmlengths[vf->current_link*2]; + if(vf->pcm_offset<0)vf->pcm_offset=0; + vf->pcm_offset+=total; + break; + }else + result=ogg_stream_packetout(&vf->os,NULL); } } } - + /* verify result */ if(vf->pcm_offset>pos || pos>ov_pcm_total(vf,-1)){ - ret=OV_EFAULT; + result=OV_EFAULT; goto seek_error; } + vf->bittrack=0.f; + vf->samptrack=0.f; return(0); - + seek_error: /* dump machine so we're in a known state */ vf->pcm_offset=-1; _decode_clear(vf); - return ret; + return (int)result; } -/* seek to a sample offset relative to the decompressed pcm stream +/* seek to a sample offset relative to the decompressed pcm stream returns zero on success, nonzero on failure */ int ov_pcm_seek(OggVorbis_File *vf,ogg_int64_t pos){ - int thisblock,lastblock=0,blockacc=0; + int thisblock,lastblock=0; int ret=ov_pcm_seek_page(vf,pos); if(ret<0)return(ret); + if((ret=_make_decode_ready(vf)))return ret; /* discard leading packets we don't need for the lapping of the position we want; don't decode them */ -#if 0 + while(1){ ogg_packet op; ogg_page og; @@ -1126,93 +1686,111 @@ int ov_pcm_seek(OggVorbis_File *vf,ogg_int64_t pos){ int ret=ogg_stream_packetpeek(&vf->os,&op); if(ret>0){ thisblock=vorbis_packet_blocksize(vf->vi+vf->current_link,&op); + if(thisblock<0){ + ogg_stream_packetout(&vf->os,NULL); + continue; /* non audio packet */ + } + if(lastblock)vf->pcm_offset+=(lastblock+thisblock)>>2; - if(blockacc+ - ((lastblock+thisblock)>>2)+ - (thisblock>>1)+vf->pcm_offset>pos)break; + if(vf->pcm_offset+((thisblock+ + vorbis_info_blocksize(vf->vi,1))>>2)>=pos)break; + /* remove the packet from packet queue and track its granulepos */ ogg_stream_packetout(&vf->os,NULL); + vorbis_synthesis_trackonly(&vf->vb,&op); /* set up a vb with + only tracking, no + pcm_decode */ + vorbis_synthesis_blockin(&vf->vd,&vf->vb); + /* end of logical stream case is hard, especially with exact length positioning. */ if(op.granulepos>-1){ - int i; - /* always believe the stream markers */ - vf->pcm_offset=op.granulepos; - for(i=0;icurrent_link;i++) - vf->pcm_offset+=vf->pcmlengths[i]; - blockacc=0; - }else - if(lastblock)blockacc+=(lastblock+thisblock)>>2; + int i; + /* always believe the stream markers */ + vf->pcm_offset=op.granulepos-vf->pcmlengths[vf->current_link*2]; + if(vf->pcm_offset<0)vf->pcm_offset=0; + for(i=0;icurrent_link;i++) + vf->pcm_offset+=vf->pcmlengths[i*2+1]; + } lastblock=thisblock; + }else{ if(ret<0 && ret!=OV_HOLE)break; - + /* suck in a new page */ if(_get_next_page(vf,&og,-1)<0)break; - if(vf->current_serialno!=ogg_page_serialno(&og))_decode_clear(vf); - + if(ogg_page_bos(&og))_decode_clear(vf); + if(vf->ready_statecurrent_serialno=ogg_page_serialno(&og); - for(link=0;linklinks;link++) - if(vf->serialnos[link]==vf->current_serialno)break; - if(link==vf->links)return(OV_EBADLINK); - vf->current_link=link; - - ogg_stream_init(&vf->os,vf->current_serialno); - ogg_stream_reset(&vf->os); - vf->ready_state=STREAMSET; - lastblock=0; + long serialno=ogg_page_serialno(&og); + int link; + + for(link=0;linklinks;link++) + if(vf->serialnos[link]==serialno)break; + if(link==vf->links) continue; + vf->current_link=link; + + vf->ready_state=STREAMSET; + vf->current_serialno=ogg_page_serialno(&og); + ogg_stream_reset_serialno(&vf->os,serialno); + ret=_make_decode_ready(vf); + if(ret)return ret; + lastblock=0; } + ogg_stream_pagein(&vf->os,&og); } } - if(lastblock)vf->pcm_offset+=blockacc; -#endif - + vf->bittrack=0.f; + vf->samptrack=0.f; /* discard samples until we reach the desired position. Crossing a logical bitstream boundary with abandon is OK. */ - _make_decode_ready(vf); - while(vf->pcm_offsetpcm_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(samplespcm_offset=ov_pcm_total(vf,-1); /* eof */ + { + /* note that halfrate could be set differently in each link, but + vorbisfile encoforces all links are set or unset */ + int hs=vorbis_synthesis_halfrate_p(vf->vi); + while(vf->pcm_offset<((pos>>hs)<pcm_offset)>>hs; + long samples=vorbis_synthesis_pcmout(&vf->vd,NULL); + + if(samples>target)samples=target; + vorbis_synthesis_read(&vf->vd,samples); + vf->pcm_offset+=samples<pcm_offset=ov_pcm_total(vf,-1); /* eof */ + } } return 0; } -/* seek to a playback time relative to the decompressed pcm stream +/* seek to a playback time relative to the decompressed pcm stream returns zero on success, nonzero on failure */ int ov_time_seek(OggVorbis_File *vf,double seconds){ /* translate time to PCM position and call ov_pcm_seek */ int link=-1; - ogg_int64_t pcm_total=ov_pcm_total(vf,-1); - double time_total=ov_time_total(vf,-1); + ogg_int64_t pcm_total=0; + double time_total=0.; if(vf->ready_stateseekable)return(OV_ENOSEEK); - if(seconds<0 || seconds>time_total)return(OV_EINVAL); - + if(seconds<0)return(OV_EINVAL); + /* 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; + for(link=0;linklinks;link++){ + double addsec = ov_time_total(vf,link); + if(secondspcmlengths[link*2+1]; } + if(link==vf->links)return(OV_EINVAL); + /* enough information to convert time offset to pcm offset */ { ogg_int64_t target=pcm_total+(seconds-time_total)*vf->vi[link].rate; @@ -1220,26 +1798,29 @@ int ov_time_seek(OggVorbis_File *vf,double seconds){ } } -/* page-granularity version of ov_time_seek +/* page-granularity version of ov_time_seek returns zero on success, nonzero on failure */ int ov_time_seek_page(OggVorbis_File *vf,double seconds){ /* translate time to PCM position and call ov_pcm_seek */ int link=-1; - ogg_int64_t pcm_total=ov_pcm_total(vf,-1); - double time_total=ov_time_total(vf,-1); + ogg_int64_t pcm_total=0; + double time_total=0.; if(vf->ready_stateseekable)return(OV_ENOSEEK); - if(seconds<0 || seconds>time_total)return(OV_EINVAL); - + if(seconds<0)return(OV_EINVAL); + /* 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; + for(link=0;linklinks;link++){ + double addsec = ov_time_total(vf,link); + if(secondspcmlengths[link*2+1]; } + if(link==vf->links)return(OV_EINVAL); + /* enough information to convert time offset to pcm offset */ { ogg_int64_t target=pcm_total+(seconds-time_total)*vf->vi[link].rate; @@ -1262,20 +1843,18 @@ ogg_int64_t ov_pcm_tell(OggVorbis_File *vf){ /* return time offset (seconds) of next PCM sample to be read */ double ov_time_tell(OggVorbis_File *vf){ - /* translate time to PCM position and call ov_pcm_seek */ - - int link=-1; + int link=0; ogg_int64_t pcm_total=0; double time_total=0.f; - + if(vf->ready_stateseekable){ 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]; + pcm_total-=vf->pcmlengths[link*2+1]; time_total-=ov_time_total(vf,link); if(vf->pcm_offset>=pcm_total)break; } @@ -1287,7 +1866,7 @@ double ov_time_tell(OggVorbis_File *vf){ /* 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 */ @@ -1296,14 +1875,14 @@ vorbis_info *ov_info(OggVorbis_File *vf,int link){ if(vf->seekable){ if(link<0) if(vf->ready_state>=STREAMSET) - return vf->vi+vf->current_link; + return vf->vi+vf->current_link; else return vf->vi; else if(link>=vf->links) - return NULL; + return NULL; else - return vf->vi+link; + return vf->vi+link; }else{ return vf->vi; } @@ -1314,20 +1893,20 @@ vorbis_comment *ov_comment(OggVorbis_File *vf,int link){ if(vf->seekable){ if(link<0) if(vf->ready_state>=STREAMSET) - return vf->vc+vf->current_link; + return vf->vc+vf->current_link; else - return vf->vc; + return vf->vc; else if(link>=vf->links) - return NULL; + return NULL; else - return vf->vc+link; + return vf->vc+link; }else{ return vf->vc; } } -int host_is_big_endian() { +static int host_is_big_endian() { ogg_int32_t pattern = 0xfeedface; /* deadbeef */ unsigned char *bytewise = (unsigned char *)&pattern; if (bytewise[0] == 0xfe) return 1; @@ -1349,150 +1928,493 @@ int host_is_big_endian() { index within the physical bitstream. Note that the accessor functions above are aware of this dichotomy). + ov_read_filter is exactly the same as ov_read except that it processes + the decoded audio data through a filter before packing it into the + requested format. This gives greater accuracy than applying a filter + after the audio has been converted into integral PCM. + 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) + 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: <0) error/hole in data (OV_HOLE), partial open (OV_EINVAL) 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. + 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 */ + *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){ +long ov_read_filter(OggVorbis_File *vf,char *buffer,int length, + int bigendianp,int word,int sgned,int *bitstream, + void (*filter)(float **pcm,long channels,long samples,void *filter_param),void *filter_param){ int i,j; int host_endian = host_is_big_endian(); + int hs; + + float **pcm; + long samples; if(vf->ready_stateready_state==OPENED)return(OV_EOF); /* stream is always - initialized after - other calls (after - open)... unless there - was no page at the end - to initialize state - with. */ while(1){ - if(vf->ready_state>=STREAMSET){ + if(vf->ready_state==INITSET){ + samples=vorbis_synthesis_pcmout(&vf->vd,&pcm); + if(samples)break; + } + + /* suck in another packet */ + { + int ret=_fetch_and_process_packet(vf,NULL,1,1); + if(ret==OV_EOF) + return(0); + if(ret<=0) + return(ret); + } + + } + + if(samples>0){ + + /* yay! proceed to pack data into the byte buffer */ + + long channels=ov_info(vf,-1)->channels; + long bytespersample=word * channels; + vorbis_fpu_control fpu; + if(samples>length/bytespersample)samples=length/bytespersample; + + if(samples <= 0) + return OV_EINVAL; + + /* Here. */ + if(filter) + filter(pcm,channels,samples,filter_param); + + /* a tight loop to pack each size */ + { + int val; + if(word==1){ + int off=(sgned?0:128); + vorbis_fpu_setround(&fpu); + for(j=0;j127)val=127; + else if(val<-128)val=-128; + *buffer++=val+off; + } + vorbis_fpu_restore(fpu); + }else{ + int off=(sgned?0:32768); + + if(host_endian==bigendianp){ + if(sgned){ + + vorbis_fpu_setround(&fpu); + for(i=0;i32767)val=32767; + else if(val<-32768)val=-32768; + *dest=val; + dest+=channels; + } + } + vorbis_fpu_restore(fpu); + + }else{ + + vorbis_fpu_setround(&fpu); + for(i=0;i32767)val=32767; + else if(val<-32768)val=-32768; + *dest=val+off; + dest+=channels; + } + } + vorbis_fpu_restore(fpu); + + } + }else if(bigendianp){ + + vorbis_fpu_setround(&fpu); + for(j=0;j32767)val=32767; + else if(val<-32768)val=-32768; + val+=off; + *buffer++=(val>>8); + *buffer++=(val&0xff); + } + vorbis_fpu_restore(fpu); + + }else{ + int val; + vorbis_fpu_setround(&fpu); + for(j=0;j32767)val=32767; + else if(val<-32768)val=-32768; + val+=off; + *buffer++=(val&0xff); + *buffer++=(val>>8); + } + vorbis_fpu_restore(fpu); + + } + } + } + + vorbis_synthesis_read(&vf->vd,samples); + hs=vorbis_synthesis_halfrate_p(vf->vi); + vf->pcm_offset+=(samples<current_link; + return(samples*bytespersample); + }else{ + return(samples); + } +} + +long ov_read(OggVorbis_File *vf,char *buffer,int length, + int bigendianp,int word,int sgned,int *bitstream){ + return ov_read_filter(vf, buffer, length, bigendianp, word, sgned, bitstream, NULL, NULL); +} + +/* input values: pcm_channels) a float vector per channel of output + length) the sample length being read by the app + + return values: <0) error/hole in data (OV_HOLE), partial open (OV_EINVAL) + 0) EOF + n) number of samples 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_float(OggVorbis_File *vf,float ***pcm_channels,int length, + int *bitstream){ + + if(vf->ready_stateready_state==INITSET){ float **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; - vorbis_fpu_control fpu; - if(samples>length/bytespersample)samples=length/bytespersample; - - /* a tight loop to pack each size */ - { - int val; - if(word==1){ - int off=(sgned?0:128); - vorbis_fpu_setround(&fpu); - for(j=0;j127)val=127; - else if(val<-128)val=-128; - *buffer++=val+off; - } - vorbis_fpu_restore(fpu); - }else{ - int off=(sgned?0:32768); - - if(host_endian==bigendianp){ - if(sgned){ - - vorbis_fpu_setround(&fpu); - for(i=0;i32767)val=32767; - else if(val<-32768)val=-32768; - *dest=val; - dest+=channels; - } - } - vorbis_fpu_restore(fpu); - - }else{ - - vorbis_fpu_setround(&fpu); - for(i=0;i32767)val=32767; - else if(val<-32768)val=-32768; - *dest=val+off; - dest+=channels; - } - } - vorbis_fpu_restore(fpu); - - } - }else if(bigendianp){ - - vorbis_fpu_setround(&fpu); - for(j=0;j32767)val=32767; - else if(val<-32768)val=-32768; - val+=off; - *buffer++=(val>>8); - *buffer++=(val&0xff); - } - vorbis_fpu_restore(fpu); - - }else{ - int val; - vorbis_fpu_setround(&fpu); - for(j=0;j32767)val=32767; - else if(val<-32768)val=-32768; - val+=off; - *buffer++=(val&0xff); - *buffer++=(val>>8); - } - vorbis_fpu_restore(fpu); - - } - } - } - - vorbis_synthesis_read(&vf->vd,samples); - vf->pcm_offset+=samples; - if(bitstream)*bitstream=vf->current_link; - return(samples*bytespersample); + int hs=vorbis_synthesis_halfrate_p(vf->vi); + if(pcm_channels)*pcm_channels=pcm; + if(samples>length)samples=length; + vorbis_synthesis_read(&vf->vd,samples); + vf->pcm_offset+=samples<current_link; + return samples; + } } /* suck in another packet */ - switch(_process_packet(vf,1)){ - case 0:case OV_EOF: - return(0); - case OV_HOLE: - return(OV_HOLE); - case OV_EBADLINK: - return(OV_EBADLINK); + { + int ret=_fetch_and_process_packet(vf,NULL,1,1); + if(ret==OV_EOF)return(0); + if(ret<=0)return(ret); + } + + } +} + +extern const float *vorbis_window(vorbis_dsp_state *v,int W); + +static void _ov_splice(float **pcm,float **lappcm, + int n1, int n2, + int ch1, int ch2, + const float *w1, const float *w2){ + int i,j; + const float *w=w1; + int n=n1; + + if(n1>n2){ + n=n2; + w=w2; + } + + /* splice */ + for(j=0;jready_state==INITSET)break; + /* suck in another packet */ + { + int ret=_fetch_and_process_packet(vf,NULL,1,0); + if(ret<0 && ret!=OV_HOLE)return(ret); + } + } + return 0; +} + +/* make sure vf is INITSET and that we have a primed buffer; if + we're crosslapping at a stream section boundary, this also makes + sure we're sanity checking against the right stream information */ +static int _ov_initprime(OggVorbis_File *vf){ + vorbis_dsp_state *vd=&vf->vd; + while(1){ + if(vf->ready_state==INITSET) + if(vorbis_synthesis_pcmout(vd,NULL))break; + + /* suck in another packet */ + { + int ret=_fetch_and_process_packet(vf,NULL,1,0); + if(ret<0 && ret!=OV_HOLE)return(ret); + } + } + return 0; +} + +/* grab enough data for lapping from vf; this may be in the form of + unreturned, already-decoded pcm, remaining PCM we will need to + decode, or synthetic postextrapolation from last packets. */ +static void _ov_getlap(OggVorbis_File *vf,vorbis_info *vi,vorbis_dsp_state *vd, + float **lappcm,int lapsize){ + int lapcount=0,i; + float **pcm; + + /* try first to decode the lapping data */ + while(lapcountlapsize-lapcount)samples=lapsize-lapcount; + for(i=0;ichannels;i++) + memcpy(lappcm[i]+lapcount,pcm[i],sizeof(**pcm)*samples); + lapcount+=samples; + vorbis_synthesis_read(vd,samples); + }else{ + /* suck in another packet */ + int ret=_fetch_and_process_packet(vf,NULL,1,0); /* do *not* span */ + if(ret==OV_EOF)break; + } + } + if(lapcountvd,&pcm); + if(samples==0){ + for(i=0;ichannels;i++) + memset(lappcm[i]+lapcount,0,sizeof(**pcm)*lapsize-lapcount); + lapcount=lapsize; + }else{ + if(samples>lapsize-lapcount)samples=lapsize-lapcount; + for(i=0;ichannels;i++) + memcpy(lappcm[i]+lapcount,pcm[i],sizeof(**pcm)*samples); + lapcount+=samples; } } } +/* this sets up crosslapping of a sample by using trailing data from + sample 1 and lapping it into the windowing buffer of sample 2 */ +int ov_crosslap(OggVorbis_File *vf1, OggVorbis_File *vf2){ + vorbis_info *vi1,*vi2; + float **lappcm; + float **pcm; + const float *w1,*w2; + int n1,n2,i,ret,hs1,hs2; + + if(vf1==vf2)return(0); /* degenerate case */ + if(vf1->ready_stateready_statechannels); + n1=vorbis_info_blocksize(vi1,0)>>(1+hs1); + n2=vorbis_info_blocksize(vi2,0)>>(1+hs2); + w1=vorbis_window(&vf1->vd,0); + w2=vorbis_window(&vf2->vd,0); + + for(i=0;ichannels;i++) + lappcm[i]=alloca(sizeof(**lappcm)*n1); + + _ov_getlap(vf1,vi1,&vf1->vd,lappcm,n1); + + /* have a lapping buffer from vf1; now to splice it into the lapping + buffer of vf2 */ + /* consolidate and expose the buffer. */ + vorbis_synthesis_lapout(&vf2->vd,&pcm); +#if 0 + _analysis_output_always("pcmL",0,pcm[0],n1*2,0,0,0); + _analysis_output_always("pcmR",0,pcm[1],n1*2,0,0,0); +#endif + + /* splice */ + _ov_splice(pcm,lappcm,n1,n2,vi1->channels,vi2->channels,w1,w2); + + /* done */ + return(0); +} + +static int _ov_64_seek_lap(OggVorbis_File *vf,ogg_int64_t pos, + int (*localseek)(OggVorbis_File *,ogg_int64_t)){ + vorbis_info *vi; + float **lappcm; + float **pcm; + const float *w1,*w2; + int n1,n2,ch1,ch2,hs; + int i,ret; + + if(vf->ready_statechannels; + n1=vorbis_info_blocksize(vi,0)>>(1+hs); + w1=vorbis_window(&vf->vd,0); /* window arrays from libvorbis are + persistent; even if the decode state + from this link gets dumped, this + window array continues to exist */ + + lappcm=alloca(sizeof(*lappcm)*ch1); + for(i=0;ivd,lappcm,n1); + + /* have lapping data; seek and prime the buffer */ + ret=localseek(vf,pos); + if(ret)return ret; + ret=_ov_initprime(vf); + if(ret)return(ret); + /* Guard against cross-link changes; they're perfectly legal */ + vi=ov_info(vf,-1); + ch2=vi->channels; + n2=vorbis_info_blocksize(vi,0)>>(1+hs); + w2=vorbis_window(&vf->vd,0); + /* consolidate and expose the buffer. */ + vorbis_synthesis_lapout(&vf->vd,&pcm); + + /* splice */ + _ov_splice(pcm,lappcm,n1,n2,ch1,ch2,w1,w2); + + /* done */ + return(0); +} + +int ov_raw_seek_lap(OggVorbis_File *vf,ogg_int64_t pos){ + return _ov_64_seek_lap(vf,pos,ov_raw_seek); +} + +int ov_pcm_seek_lap(OggVorbis_File *vf,ogg_int64_t pos){ + return _ov_64_seek_lap(vf,pos,ov_pcm_seek); +} + +int ov_pcm_seek_page_lap(OggVorbis_File *vf,ogg_int64_t pos){ + return _ov_64_seek_lap(vf,pos,ov_pcm_seek_page); +} + +static int _ov_d_seek_lap(OggVorbis_File *vf,double pos, + int (*localseek)(OggVorbis_File *,double)){ + vorbis_info *vi; + float **lappcm; + float **pcm; + const float *w1,*w2; + int n1,n2,ch1,ch2,hs; + int i,ret; + + if(vf->ready_statechannels; + n1=vorbis_info_blocksize(vi,0)>>(1+hs); + w1=vorbis_window(&vf->vd,0); /* window arrays from libvorbis are + persistent; even if the decode state + from this link gets dumped, this + window array continues to exist */ + + lappcm=alloca(sizeof(*lappcm)*ch1); + for(i=0;ivd,lappcm,n1); + + /* have lapping data; seek and prime the buffer */ + ret=localseek(vf,pos); + if(ret)return ret; + ret=_ov_initprime(vf); + if(ret)return(ret); + + /* Guard against cross-link changes; they're perfectly legal */ + vi=ov_info(vf,-1); + ch2=vi->channels; + n2=vorbis_info_blocksize(vi,0)>>(1+hs); + w2=vorbis_window(&vf->vd,0); + + /* consolidate and expose the buffer. */ + vorbis_synthesis_lapout(&vf->vd,&pcm); + + /* splice */ + _ov_splice(pcm,lappcm,n1,n2,ch1,ch2,w1,w2); + + /* done */ + return(0); +} + +int ov_time_seek_lap(OggVorbis_File *vf,double pos){ + return _ov_d_seek_lap(vf,pos,ov_time_seek); +} + +int ov_time_seek_page_lap(OggVorbis_File *vf,double pos){ + return _ov_d_seek_lap(vf,pos,ov_time_seek_page); +}