summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMonty <xiphmont@xiph.org>2009-04-09 15:34:27 +0000
committerMonty <xiphmont@xiph.org>2009-04-09 15:34:27 +0000
commita172bba24bdea2283e9dfcc5bf3643623730a2d6 (patch)
treee9ddbf41af7f6dffea20bde0834392241cc47312
parentc62e2bd601db5002f36d851d15dd19374d5aae89 (diff)
downloadlibvorbis-git-a172bba24bdea2283e9dfcc5bf3643623730a2d6.tar.gz
Complete first-cut rearrangement of bisection and initialization code to reduce stream seeks.
svn path=/trunk/vorbis/; revision=15928
-rw-r--r--include/vorbis/vorbisfile.h3
-rw-r--r--lib/vorbisfile.c454
2 files changed, 243 insertions, 214 deletions
diff --git a/include/vorbis/vorbisfile.h b/include/vorbis/vorbisfile.h
index e128fe88..7c6cbc4d 100644
--- a/include/vorbis/vorbisfile.h
+++ b/include/vorbis/vorbisfile.h
@@ -48,9 +48,12 @@ typedef struct {
* ov_open() to avoid problems with incompatable crt.o version linking
* issues. */
+#include <stdio.h>
static int _ov_header_fseek_wrap(FILE *f,ogg_int64_t off,int whence){
if(f==NULL)return(-1);
+ fprintf(stderr,"seek: %s %ld\n",(whence==SEEK_END?"END":(whence==SEEK_SET?"SET":"CUR")), (long)off);
+
#ifdef __MINGW32__
return fseeko64(f,off,whence);
#elif defined (_WIN32)
diff --git a/lib/vorbisfile.c b/lib/vorbisfile.c
index 88a53fc8..5308696d 100644
--- a/lib/vorbisfile.c
+++ b/lib/vorbisfile.c
@@ -167,7 +167,7 @@ static ogg_int64_t _get_prev_page(OggVorbis_File *vf,ogg_page *og){
/* In a fully compliant, non-multiplexed stream, we'll still be
holding the last page. In multiplexed (or noncompliant streams),
- we may need to re-read the last page we saw */
+ 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);
@@ -181,6 +181,59 @@ static ogg_int64_t _get_prev_page(OggVorbis_File *vf,ogg_page *og){
return(offset);
}
+/* 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,int *serialno, ogg_int64_t *granpos){
+ ogg_page og;
+ ogg_int64_t begin=vf->offset;
+ ogg_int64_t end=begin;
+ ogg_int64_t ret;
+
+ ogg_int64_t prefoffset=-1;
+ ogg_int64_t offset=-1;
+ ogg_int64_t ret_serialno;
+ ogg_int64_t ret_gran;
+
+ while(offset==-1){
+ begin-=CHUNKSIZE;
+ if(begin<0)
+ begin=0;
+
+ ret=_seek_helper(vf,begin);
+ if(ret)return(ret);
+
+ while(vf->offset<end){
+ ret=_get_next_page(vf,&og,end-vf->offset);
+ 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;
+ }
+
+ }
+ }
+ }
+
+ /* 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);
+
+}
+
static void _add_serialno(ogg_page *og,long **serialno_list, int *n){
long s = ogg_page_serialno(og);
(*n)++;
@@ -195,9 +248,7 @@ static void _add_serialno(ogg_page *og,long **serialno_list, int *n){
}
/* returns nonzero if found */
-static int _lookup_serialno(ogg_page *og, long *serialno_list, int n){
- long s = ogg_page_serialno(og);
-
+static int _lookup_serialno(long s, long *serialno_list, int n){
if(serialno_list){
while(n--){
if(*serialno_list == s) return 1;
@@ -207,102 +258,15 @@ static int _lookup_serialno(ogg_page *og, long *serialno_list, int n){
return 0;
}
-/* start parsing pages at current offset, remembering all serial
- numbers. Stop logging at first non-bos page */
-static int _get_serialnos(OggVorbis_File *vf, long **s, int *n){
- ogg_page og;
-
- *s=NULL;
- *n=0;
-
- while(1){
- ogg_int64_t llret=_get_next_page(vf,&og,CHUNKSIZE);
- if(llret==OV_EOF)return(0);
- if(llret<0)return(llret);
- if(!ogg_page_bos(&og)) return 0;
-
- /* look for duplicate serialnos; add this one if unique */
- if(_lookup_serialno(&og,*s,*n)){
- if(*s)_ogg_free(*s);
- *s=0;
- *n=0;
- return(OV_EBADHEADER);
- }
-
- _add_serialno(&og,s,n);
- }
-}
-
-/* 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,
- long *currentno_list,
- int currentnos,
- long m){
- ogg_int64_t endsearched=end;
- ogg_int64_t next=end;
- ogg_page og;
- ogg_int64_t ret;
-
- /* the below guards against garbage seperating the last and
- first pages of two links. */
- while(searched<endsearched){
- ogg_int64_t bisect;
-
- if(endsearched-searched<CHUNKSIZE){
- bisect=searched;
- }else{
- bisect=(searched+endsearched)/2;
- }
-
- ret=_seek_helper(vf,bisect);
- if(ret)return(ret);
-
- ret=_get_next_page(vf,&og,-1);
- if(ret==OV_EREAD)return(OV_EREAD);
- if(ret<0 || !_lookup_serialno(&og,currentno_list,currentnos)){
- endsearched=bisect;
- if(ret>=0)next=ret;
- }else{
- searched=ret+og.header_len+og.body_len;
- }
- }
-
- {
- long *next_serialno_list=NULL;
- int next_serialnos=0;
-
- ret=_seek_helper(vf,next);
- if(ret)return(ret);
- ret=_get_serialnos(vf,&next_serialno_list,&next_serialnos);
- if(ret)return(ret);
-
- if(searched>=end || next_serialnos==0){
- vf->links=m+1;
- if(vf->offsets)_ogg_free(vf->offsets);
- vf->offsets=_ogg_malloc((vf->links+1)*sizeof(*vf->offsets));
- vf->offsets[m+1]=searched;
- }else{
- ret=_bisect_forward_serialno(vf,next,vf->offset,
- end,next_serialno_list,next_serialnos,m+1);
- if(ret)return(ret);
- }
-
- if(next_serialno_list)_ogg_free(next_serialno_list);
- }
- vf->offsets[m]=begin;
- return(0);
+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);
}
/* 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, long **serialno_list, int *serialno_n,
+ long **serialno_list, int *serialno_n,
ogg_page *og_ptr){
ogg_page og;
ogg_packet op;
@@ -318,13 +282,14 @@ static int _fetch_headers(OggVorbis_File *vf,vorbis_info *vi,vorbis_comment *vc,
vorbis_info_init(vi);
vorbis_comment_init(vc);
+ 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_serialno(og_ptr,*serialno_list,*serialno_n)){
+ 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;
@@ -345,7 +310,6 @@ static int _fetch_headers(OggVorbis_File *vf,vorbis_info *vi,vorbis_comment *vc,
if(ogg_stream_packetout(&vf->os,&op) > 0 &&
vorbis_synthesis_idheader(&op)){
/* vorbis header; continue setup */
- if(serialno)*serialno=vf->os.serialno;
vf->ready_state=STREAMSET;
if((ret=vorbis_synthesis_headerin(vi,vc,&op))){
ret=OV_EBADHEADER;
@@ -437,132 +401,183 @@ static int _fetch_headers(OggVorbis_File *vf,vorbis_info *vi,vorbis_comment *vc,
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;
-/* 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, ogg_int64_t dataoffset){
- ogg_page og;
- int i;
- ogg_int64_t ret;
+ 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;
+
+ /* 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(lastblock!=-1)
+ accumulated+=(lastblock+thisblock)>>2;
+ lastblock=thisblock;
+ }
+ }
- if(vf->serialnos)_ogg_free(vf->serialnos);
- if(vf->dataoffsets)_ogg_free(vf->dataoffsets);
+ if(ogg_page_granulepos(&og)!=-1){
+ /* pcm offset of last packet on the first audio page */
+ accumulated= ogg_page_granulepos(&og)-accumulated;
+ break;
+ }
+ }
- 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));
+ /* less than zero? This is a stream with samples trimmed off
+ the beginning, a normal occurrence; set the offset to zero */
+ if(accumulated<0)accumulated=0;
- for(i=0;i<vf->links;i++){
- if(i==0){
- /* we already grabbed the initial header earlier. Just set the offset */
- vf->serialnos[i]=vf->current_serialno;
- vf->dataoffsets[i]=dataoffset;
- ret=_seek_helper(vf,dataoffset);
- if(ret)
- vf->dataoffsets[i]=-1;
-
- }else{
+ return accumulated;
+}
- /* seek to the location of the initial header */
+/* 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;
+ ogg_int64_t ret,last;
+ int serialno = vf->os.serialno;
- ret=_seek_helper(vf,vf->offsets[i]);
- if(ret){
- vf->dataoffsets[i]=-1;
- }else{
- if(_fetch_headers(vf,vf->vi+i,vf->vc+i,vf->serialnos+i,NULL,NULL,NULL)<0){
- vf->dataoffsets[i]=-1;
- }else{
- vf->dataoffsets[i]=vf->offset;
- }
- }
- }
+ /* 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)
+ */
- /* fetch beginning PCM offset */
+ /* Is the last page in our list of current serialnumbers? */
+ if(_lookup_serialno(endserial,currentno_list,currentnos)){
- if(vf->dataoffsets[i]!=-1){
- ogg_int64_t accumulated=0;
- long lastblock=-1;
- int result;
+ /* 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. */
+
+ while(endserial != serialno){
+ endserial = serialno;
+ vf->offset=_get_prev_page_serial(vf,&endserial,&endgran);
+ }
- ogg_stream_reset_serialno(&vf->os,vf->serialnos[i]);
+ 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);
- while(1){
- ogg_packet op;
+ 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));
- ret=_get_next_page(vf,&og,-1);
- if(ret<0)
- /* this should not be possible unless the file is
- truncated/mangled */
- break;
-
- if(ogg_page_bos(&og)) break;
+ vf->offsets[m+1]=end;
+ vf->offsets[m]=begin;
+ vf->pcmlengths[m*2+1]=endgran;
- if(ogg_page_serialno(&og)!=vf->serialnos[i])
- continue;
-
- /* 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(vf->vi+i,&op);
- if(lastblock!=-1)
- accumulated+=(lastblock+thisblock)>>2;
- lastblock=thisblock;
- }
- }
+ }else{
+
+ long *next_serialno_list=NULL;
+ int next_serialnos=0;
+ vorbis_info vi;
+ vorbis_comment vc;
- if(ogg_page_granulepos(&og)!=-1){
- /* pcm offset of last packet on the first audio page */
- accumulated= ogg_page_granulepos(&og)-accumulated;
- break;
- }
+ /* the below guards against garbage seperating the last and
+ first pages of two links. */
+ while(searched<endsearched){
+ ogg_int64_t bisect;
+
+ if(endsearched-searched<CHUNKSIZE){
+ bisect=searched;
+ }else{
+ bisect=(searched+endsearched)/2;
}
+
+ ret=_seek_helper(vf,bisect);
+ if(ret)return(ret);
- /* less than zero? This is a stream with samples trimmed off
- the beginning, a normal occurrence; set the offset to zero */
- if(accumulated<0)accumulated=0;
-
- vf->pcmlengths[i*2]=accumulated;
+ 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=last+og.header_len+og.body_len;
+ }
}
- /* get the PCM length of this link. To do this,
- get the last page of the stream */
+ /* Bisection point found */
+
+ /* for the time being, fetch end PCM offset the simple way */
{
- ogg_int64_t end=vf->offsets[i+1];
- ret=_seek_helper(vf,end);
- if(ret){
- /* this should not be possible */
- vorbis_info_clear(vf->vi+i);
- vorbis_comment_clear(vf->vc+i);
- }else{
-
- 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_serialno(&og)==vf->serialnos[i]){
- if(ogg_page_granulepos(&og)!=-1){
- vf->pcmlengths[i*2+1]=ogg_page_granulepos(&og)-vf->pcmlengths[i*2];
- break;
- }
- }
- vf->offset=ret;
- }
+ int testserial = serialno+1;
+ vf->offset = next;
+ while(testserial != serialno){
+ testserial = serialno;
+ vf->offset=_get_prev_page_serial(vf,&testserial,&searchgran);
}
}
+
+ if(vf->offset!=next){
+ 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 bistection 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;
+
}
+ return(0);
}
static int _make_decode_ready(OggVorbis_File *vf){
@@ -583,11 +598,16 @@ static int _make_decode_ready(OggVorbis_File *vf){
}
static int _open_seekable2(OggVorbis_File *vf){
- ogg_int64_t dataoffset=vf->dataoffsets[0],end;
- 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 */
if(vf->callbacks.seek_func && vf->callbacks.tell_func){
(vf->callbacks.seek_func)(vf->datasource,0,SEEK_END);
@@ -599,16 +619,22 @@ static int _open_seekable2(OggVorbis_File *vf){
/* If seek_func is implemented, tell_func must also be implemented */
if(vf->end==-1) return(OV_EINVAL);
- /* 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);
+ /* 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,&endserial,&endgran);
if(end<0)return(end);
/* now determine bitstream structure recursively */
- if(_bisect_forward_serialno(vf,0,0,end+1,vf->serialnos+2,vf->serialnos[1],0)<0)return(OV_EREAD);
+ if(_bisect_forward_serialno(vf,0,dataoffset,end+1,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;
- /* the initial header memory is referenced by vf after; don't free it */
- _prefetch_all_headers(vf,dataoffset);
return(ov_raw_seek(vf,dataoffset));
}
@@ -802,8 +828,9 @@ static int _fetch_and_process_packet(OggVorbis_File *vf,
/* we're streaming */
/* fetch the three header packets, build the info struct */
- int ret=_fetch_headers(vf,vf->vi,vf->vc,&vf->current_serialno,NULL,NULL,&og);
+ 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;
}
@@ -865,9 +892,7 @@ static int _ov_open1(void *f,OggVorbis_File *vf,char *initial,
/* 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,&vf->current_serialno,
- &serialno_list,&serialno_list_size,
- NULL))<0){
+ if((ret=_fetch_headers(vf,vf->vi,vf->vc,&serialno_list,&serialno_list_size,NULL))<0){
vf->datasource=NULL;
ov_clear(vf);
}else{
@@ -883,6 +908,7 @@ static int _ov_open1(void *f,OggVorbis_File *vf,char *initial,
vf->dataoffsets=_ogg_calloc(1,sizeof(*vf->dataoffsets));
vf->offsets[0]=0;
vf->dataoffsets[0]=vf->offset;
+ vf->current_serialno=vf->os.serialno;
vf->ready_state=PARTOPEN;
}