nuclear@1: /******************************************************************** nuclear@1: * * nuclear@1: * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * nuclear@1: * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * nuclear@1: * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * nuclear@1: * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * nuclear@1: * * nuclear@1: * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2009 * nuclear@1: * by the Xiph.Org Foundation http://www.xiph.org/ * nuclear@1: * * nuclear@1: ******************************************************************** nuclear@1: nuclear@1: function: stdio-based convenience library for opening/seeking/decoding nuclear@1: last mod: $Id: vorbisfile.c 17573 2010-10-27 14:53:59Z xiphmont $ nuclear@1: nuclear@1: ********************************************************************/ nuclear@1: nuclear@1: #include nuclear@1: #include nuclear@1: #include nuclear@1: #include nuclear@1: #include nuclear@1: nuclear@1: #include "vorbis/codec.h" nuclear@1: nuclear@1: /* we don't need or want the static callback symbols here */ nuclear@1: #define OV_EXCLUDE_STATIC_CALLBACKS nuclear@1: #include "vorbis/vorbisfile.h" nuclear@1: nuclear@1: #include "os.h" nuclear@1: #include "misc.h" nuclear@1: nuclear@1: /* A 'chained bitstream' is a Vorbis bitstream that contains more than nuclear@1: one logical bitstream arranged end to end (the only form of Ogg nuclear@1: multiplexing allowed in a Vorbis bitstream; grouping [parallel nuclear@1: multiplexing] is not allowed in Vorbis) */ nuclear@1: nuclear@1: /* A Vorbis file can be played beginning to end (streamed) without nuclear@1: worrying ahead of time about chaining (see decoder_example.c). If nuclear@1: we have the whole file, however, and want random access nuclear@1: (seeking/scrubbing) or desire to know the total length/time of a nuclear@1: file, we need to account for the possibility of chaining. */ nuclear@1: nuclear@1: /* We can handle things a number of ways; we can determine the entire nuclear@1: bitstream structure right off the bat, or find pieces on demand. nuclear@1: This example determines and caches structure for the entire nuclear@1: bitstream, but builds a virtual decoder on the fly when moving nuclear@1: between links in the chain. */ nuclear@1: nuclear@1: /* There are also different ways to implement seeking. Enough nuclear@1: information exists in an Ogg bitstream to seek to nuclear@1: sample-granularity positions in the output. Or, one can seek by nuclear@1: picking some portion of the stream roughly in the desired area if nuclear@1: we only want coarse navigation through the stream. */ nuclear@1: nuclear@1: /************************************************************************* nuclear@1: * Many, many internal helpers. The intention is not to be confusing; nuclear@1: * rampant duplication and monolithic function implementation would be nuclear@1: * harder to understand anyway. The high level functions are last. Begin nuclear@1: * grokking near the end of the file */ nuclear@1: nuclear@1: /* read a little more data from the file/pipe into the ogg_sync framer nuclear@1: */ nuclear@1: #define CHUNKSIZE 65536 /* greater-than-page-size granularity seeking */ nuclear@1: #define READSIZE 2048 /* a smaller read size is needed for low-rate streaming. */ nuclear@1: nuclear@1: static long _get_data(OggVorbis_File *vf){ nuclear@1: errno=0; nuclear@1: if(!(vf->callbacks.read_func))return(-1); nuclear@1: if(vf->datasource){ nuclear@1: char *buffer=ogg_sync_buffer(&vf->oy,READSIZE); nuclear@1: long bytes=(vf->callbacks.read_func)(buffer,1,READSIZE,vf->datasource); nuclear@1: if(bytes>0)ogg_sync_wrote(&vf->oy,bytes); nuclear@1: if(bytes==0 && errno)return(-1); nuclear@1: return(bytes); nuclear@1: }else nuclear@1: return(0); nuclear@1: } nuclear@1: nuclear@1: /* save a tiny smidge of verbosity to make the code more readable */ nuclear@1: static int _seek_helper(OggVorbis_File *vf,ogg_int64_t offset){ nuclear@1: if(vf->datasource){ nuclear@1: if(!(vf->callbacks.seek_func)|| nuclear@1: (vf->callbacks.seek_func)(vf->datasource, offset, SEEK_SET) == -1) nuclear@1: return OV_EREAD; nuclear@1: vf->offset=offset; nuclear@1: ogg_sync_reset(&vf->oy); nuclear@1: }else{ nuclear@1: /* shouldn't happen unless someone writes a broken callback */ nuclear@1: return OV_EFAULT; nuclear@1: } nuclear@1: return 0; nuclear@1: } nuclear@1: nuclear@1: /* The read/seek functions track absolute position within the stream */ nuclear@1: nuclear@1: /* from the head of the stream, get the next page. boundary specifies nuclear@1: if the function is allowed to fetch more data from the stream (and nuclear@1: how much) or only use internally buffered data. nuclear@1: nuclear@1: boundary: -1) unbounded search nuclear@1: 0) read no additional data; use cached only nuclear@1: n) search for a new page beginning for n bytes nuclear@1: nuclear@1: return: <0) did not find a page (OV_FALSE, OV_EOF, OV_EREAD) nuclear@1: n) found a page at absolute offset n */ nuclear@1: nuclear@1: static ogg_int64_t _get_next_page(OggVorbis_File *vf,ogg_page *og, nuclear@1: ogg_int64_t boundary){ nuclear@1: if(boundary>0)boundary+=vf->offset; nuclear@1: while(1){ nuclear@1: long more; nuclear@1: nuclear@1: if(boundary>0 && vf->offset>=boundary)return(OV_FALSE); nuclear@1: more=ogg_sync_pageseek(&vf->oy,og); nuclear@1: nuclear@1: if(more<0){ nuclear@1: /* skipped n bytes */ nuclear@1: vf->offset-=more; nuclear@1: }else{ nuclear@1: if(more==0){ nuclear@1: /* send more paramedics */ nuclear@1: if(!boundary)return(OV_FALSE); nuclear@1: { nuclear@1: long ret=_get_data(vf); nuclear@1: if(ret==0)return(OV_EOF); nuclear@1: if(ret<0)return(OV_EREAD); nuclear@1: } nuclear@1: }else{ nuclear@1: /* got a page. Return the offset at the page beginning, nuclear@1: advance the internal offset past the page end */ nuclear@1: ogg_int64_t ret=vf->offset; nuclear@1: vf->offset+=more; nuclear@1: return(ret); nuclear@1: nuclear@1: } nuclear@1: } nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: /* find the latest page beginning before the current stream cursor nuclear@1: position. Much dirtier than the above as Ogg doesn't have any nuclear@1: backward search linkage. no 'readp' as it will certainly have to nuclear@1: read. */ nuclear@1: /* returns offset or OV_EREAD, OV_FAULT */ nuclear@1: static ogg_int64_t _get_prev_page(OggVorbis_File *vf,ogg_page *og){ nuclear@1: ogg_int64_t begin=vf->offset; nuclear@1: ogg_int64_t end=begin; nuclear@1: ogg_int64_t ret; nuclear@1: ogg_int64_t offset=-1; nuclear@1: nuclear@1: while(offset==-1){ nuclear@1: begin-=CHUNKSIZE; nuclear@1: if(begin<0) nuclear@1: begin=0; nuclear@1: nuclear@1: ret=_seek_helper(vf,begin); nuclear@1: if(ret)return(ret); nuclear@1: nuclear@1: while(vf->offsetoffset); nuclear@1: if(ret==OV_EREAD)return(OV_EREAD); nuclear@1: if(ret<0){ nuclear@1: break; nuclear@1: }else{ nuclear@1: offset=ret; nuclear@1: } nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: /* In a fully compliant, non-multiplexed stream, we'll still be nuclear@1: holding the last page. In multiplexed (or noncompliant streams), nuclear@1: we will probably have to re-read the last page we saw */ nuclear@1: if(og->header_len==0){ nuclear@1: ret=_seek_helper(vf,offset); nuclear@1: if(ret)return(ret); nuclear@1: nuclear@1: ret=_get_next_page(vf,og,CHUNKSIZE); nuclear@1: if(ret<0) nuclear@1: /* this shouldn't be possible */ nuclear@1: return(OV_EFAULT); nuclear@1: } nuclear@1: nuclear@1: return(offset); nuclear@1: } nuclear@1: nuclear@1: static void _add_serialno(ogg_page *og,long **serialno_list, int *n){ nuclear@1: long s = ogg_page_serialno(og); nuclear@1: (*n)++; nuclear@1: nuclear@1: if(*serialno_list){ nuclear@1: *serialno_list = _ogg_realloc(*serialno_list, sizeof(**serialno_list)*(*n)); nuclear@1: }else{ nuclear@1: *serialno_list = _ogg_malloc(sizeof(**serialno_list)); nuclear@1: } nuclear@1: nuclear@1: (*serialno_list)[(*n)-1] = s; nuclear@1: } nuclear@1: nuclear@1: /* returns nonzero if found */ nuclear@1: static int _lookup_serialno(long s, long *serialno_list, int n){ nuclear@1: if(serialno_list){ nuclear@1: while(n--){ nuclear@1: if(*serialno_list == s) return 1; nuclear@1: serialno_list++; nuclear@1: } nuclear@1: } nuclear@1: return 0; nuclear@1: } nuclear@1: nuclear@1: static int _lookup_page_serialno(ogg_page *og, long *serialno_list, int n){ nuclear@1: long s = ogg_page_serialno(og); nuclear@1: return _lookup_serialno(s,serialno_list,n); nuclear@1: } nuclear@1: nuclear@1: /* performs the same search as _get_prev_page, but prefers pages of nuclear@1: the specified serial number. If a page of the specified serialno is nuclear@1: spotted during the seek-back-and-read-forward, it will return the nuclear@1: info of last page of the matching serial number instead of the very nuclear@1: last page. If no page of the specified serialno is seen, it will nuclear@1: return the info of last page and alter *serialno. */ nuclear@1: static ogg_int64_t _get_prev_page_serial(OggVorbis_File *vf, nuclear@1: long *serial_list, int serial_n, nuclear@1: int *serialno, ogg_int64_t *granpos){ nuclear@1: ogg_page og; nuclear@1: ogg_int64_t begin=vf->offset; nuclear@1: ogg_int64_t end=begin; nuclear@1: ogg_int64_t ret; nuclear@1: nuclear@1: ogg_int64_t prefoffset=-1; nuclear@1: ogg_int64_t offset=-1; nuclear@1: ogg_int64_t ret_serialno=-1; nuclear@1: ogg_int64_t ret_gran=-1; nuclear@1: nuclear@1: while(offset==-1){ nuclear@1: begin-=CHUNKSIZE; nuclear@1: if(begin<0) nuclear@1: begin=0; nuclear@1: nuclear@1: ret=_seek_helper(vf,begin); nuclear@1: if(ret)return(ret); nuclear@1: nuclear@1: while(vf->offsetoffset); nuclear@1: if(ret==OV_EREAD)return(OV_EREAD); nuclear@1: if(ret<0){ nuclear@1: break; nuclear@1: }else{ nuclear@1: ret_serialno=ogg_page_serialno(&og); nuclear@1: ret_gran=ogg_page_granulepos(&og); nuclear@1: offset=ret; nuclear@1: nuclear@1: if(ret_serialno == *serialno){ nuclear@1: prefoffset=ret; nuclear@1: *granpos=ret_gran; nuclear@1: } nuclear@1: nuclear@1: if(!_lookup_serialno(ret_serialno,serial_list,serial_n)){ nuclear@1: /* we fell off the end of the link, which means we seeked nuclear@1: back too far and shouldn't have been looking in that link nuclear@1: to begin with. If we found the preferred serial number, nuclear@1: forget that we saw it. */ nuclear@1: prefoffset=-1; nuclear@1: } nuclear@1: } nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: /* we're not interested in the page... just the serialno and granpos. */ nuclear@1: if(prefoffset>=0)return(prefoffset); nuclear@1: nuclear@1: *serialno = ret_serialno; nuclear@1: *granpos = ret_gran; nuclear@1: return(offset); nuclear@1: nuclear@1: } nuclear@1: nuclear@1: /* uses the local ogg_stream storage in vf; this is important for nuclear@1: non-streaming input sources */ nuclear@1: static int _fetch_headers(OggVorbis_File *vf,vorbis_info *vi,vorbis_comment *vc, nuclear@1: long **serialno_list, int *serialno_n, nuclear@1: ogg_page *og_ptr){ nuclear@1: ogg_page og; nuclear@1: ogg_packet op; nuclear@1: int i,ret; nuclear@1: int allbos=0; nuclear@1: nuclear@1: if(!og_ptr){ nuclear@1: ogg_int64_t llret=_get_next_page(vf,&og,CHUNKSIZE); nuclear@1: if(llret==OV_EREAD)return(OV_EREAD); nuclear@1: if(llret<0)return(OV_ENOTVORBIS); nuclear@1: og_ptr=&og; nuclear@1: } nuclear@1: nuclear@1: vorbis_info_init(vi); nuclear@1: vorbis_comment_init(vc); nuclear@1: vf->ready_state=OPENED; nuclear@1: nuclear@1: /* extract the serialnos of all BOS pages + the first set of vorbis nuclear@1: headers we see in the link */ nuclear@1: nuclear@1: while(ogg_page_bos(og_ptr)){ nuclear@1: if(serialno_list){ nuclear@1: if(_lookup_page_serialno(og_ptr,*serialno_list,*serialno_n)){ nuclear@1: /* a dupe serialnumber in an initial header packet set == invalid stream */ nuclear@1: if(*serialno_list)_ogg_free(*serialno_list); nuclear@1: *serialno_list=0; nuclear@1: *serialno_n=0; nuclear@1: ret=OV_EBADHEADER; nuclear@1: goto bail_header; nuclear@1: } nuclear@1: nuclear@1: _add_serialno(og_ptr,serialno_list,serialno_n); nuclear@1: } nuclear@1: nuclear@1: if(vf->ready_stateos,ogg_page_serialno(og_ptr)); nuclear@1: ogg_stream_pagein(&vf->os,og_ptr); nuclear@1: nuclear@1: if(ogg_stream_packetout(&vf->os,&op) > 0 && nuclear@1: vorbis_synthesis_idheader(&op)){ nuclear@1: /* vorbis header; continue setup */ nuclear@1: vf->ready_state=STREAMSET; nuclear@1: if((ret=vorbis_synthesis_headerin(vi,vc,&op))){ nuclear@1: ret=OV_EBADHEADER; nuclear@1: goto bail_header; nuclear@1: } nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: /* get next page */ nuclear@1: { nuclear@1: ogg_int64_t llret=_get_next_page(vf,og_ptr,CHUNKSIZE); nuclear@1: if(llret==OV_EREAD){ nuclear@1: ret=OV_EREAD; nuclear@1: goto bail_header; nuclear@1: } nuclear@1: if(llret<0){ nuclear@1: ret=OV_ENOTVORBIS; nuclear@1: goto bail_header; nuclear@1: } nuclear@1: nuclear@1: /* if this page also belongs to our vorbis stream, submit it and break */ nuclear@1: if(vf->ready_state==STREAMSET && nuclear@1: vf->os.serialno == ogg_page_serialno(og_ptr)){ nuclear@1: ogg_stream_pagein(&vf->os,og_ptr); nuclear@1: break; nuclear@1: } nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: if(vf->ready_state!=STREAMSET){ nuclear@1: ret = OV_ENOTVORBIS; nuclear@1: goto bail_header; nuclear@1: } nuclear@1: nuclear@1: while(1){ nuclear@1: nuclear@1: i=0; nuclear@1: while(i<2){ /* get a page loop */ nuclear@1: nuclear@1: while(i<2){ /* get a packet loop */ nuclear@1: nuclear@1: int result=ogg_stream_packetout(&vf->os,&op); nuclear@1: if(result==0)break; nuclear@1: if(result==-1){ nuclear@1: ret=OV_EBADHEADER; nuclear@1: goto bail_header; nuclear@1: } nuclear@1: nuclear@1: if((ret=vorbis_synthesis_headerin(vi,vc,&op))) nuclear@1: goto bail_header; nuclear@1: nuclear@1: i++; nuclear@1: } nuclear@1: nuclear@1: while(i<2){ nuclear@1: if(_get_next_page(vf,og_ptr,CHUNKSIZE)<0){ nuclear@1: ret=OV_EBADHEADER; nuclear@1: goto bail_header; nuclear@1: } nuclear@1: nuclear@1: /* if this page belongs to the correct stream, go parse it */ nuclear@1: if(vf->os.serialno == ogg_page_serialno(og_ptr)){ nuclear@1: ogg_stream_pagein(&vf->os,og_ptr); nuclear@1: break; nuclear@1: } nuclear@1: nuclear@1: /* if we never see the final vorbis headers before the link nuclear@1: ends, abort */ nuclear@1: if(ogg_page_bos(og_ptr)){ nuclear@1: if(allbos){ nuclear@1: ret = OV_EBADHEADER; nuclear@1: goto bail_header; nuclear@1: }else nuclear@1: allbos=1; nuclear@1: } nuclear@1: nuclear@1: /* otherwise, keep looking */ nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: return 0; nuclear@1: } nuclear@1: nuclear@1: bail_header: nuclear@1: vorbis_info_clear(vi); nuclear@1: vorbis_comment_clear(vc); nuclear@1: vf->ready_state=OPENED; nuclear@1: nuclear@1: return ret; nuclear@1: } nuclear@1: nuclear@1: /* Starting from current cursor position, get initial PCM offset of nuclear@1: next page. Consumes the page in the process without decoding nuclear@1: audio, however this is only called during stream parsing upon nuclear@1: seekable open. */ nuclear@1: static ogg_int64_t _initial_pcmoffset(OggVorbis_File *vf, vorbis_info *vi){ nuclear@1: ogg_page og; nuclear@1: ogg_int64_t accumulated=0; nuclear@1: long lastblock=-1; nuclear@1: int result; nuclear@1: int serialno = vf->os.serialno; nuclear@1: nuclear@1: while(1){ nuclear@1: ogg_packet op; nuclear@1: if(_get_next_page(vf,&og,-1)<0) nuclear@1: break; /* should not be possible unless the file is truncated/mangled */ nuclear@1: nuclear@1: if(ogg_page_bos(&og)) break; nuclear@1: if(ogg_page_serialno(&og)!=serialno) continue; nuclear@1: nuclear@1: /* count blocksizes of all frames in the page */ nuclear@1: ogg_stream_pagein(&vf->os,&og); nuclear@1: while((result=ogg_stream_packetout(&vf->os,&op))){ nuclear@1: if(result>0){ /* ignore holes */ nuclear@1: long thisblock=vorbis_packet_blocksize(vi,&op); nuclear@1: if(lastblock!=-1) nuclear@1: accumulated+=(lastblock+thisblock)>>2; nuclear@1: lastblock=thisblock; nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: if(ogg_page_granulepos(&og)!=-1){ nuclear@1: /* pcm offset of last packet on the first audio page */ nuclear@1: accumulated= ogg_page_granulepos(&og)-accumulated; nuclear@1: break; nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: /* less than zero? Either a corrupt file or a stream with samples nuclear@1: trimmed off the beginning, a normal occurrence; in both cases set nuclear@1: the offset to zero */ nuclear@1: if(accumulated<0)accumulated=0; nuclear@1: nuclear@1: return accumulated; nuclear@1: } nuclear@1: nuclear@1: /* finds each bitstream link one at a time using a bisection search nuclear@1: (has to begin by knowing the offset of the lb's initial page). nuclear@1: Recurses for each link so it can alloc the link storage after nuclear@1: finding them all, then unroll and fill the cache at the same time */ nuclear@1: static int _bisect_forward_serialno(OggVorbis_File *vf, nuclear@1: ogg_int64_t begin, nuclear@1: ogg_int64_t searched, nuclear@1: ogg_int64_t end, nuclear@1: ogg_int64_t endgran, nuclear@1: int endserial, nuclear@1: long *currentno_list, nuclear@1: int currentnos, nuclear@1: long m){ nuclear@1: ogg_int64_t pcmoffset; nuclear@1: ogg_int64_t dataoffset=searched; nuclear@1: ogg_int64_t endsearched=end; nuclear@1: ogg_int64_t next=end; nuclear@1: ogg_int64_t searchgran=-1; nuclear@1: ogg_page og; nuclear@1: ogg_int64_t ret,last; nuclear@1: int serialno = vf->os.serialno; nuclear@1: nuclear@1: /* invariants: nuclear@1: we have the headers and serialnos for the link beginning at 'begin' nuclear@1: we have the offset and granpos of the last page in the file (potentially nuclear@1: not a page we care about) nuclear@1: */ nuclear@1: nuclear@1: /* Is the last page in our list of current serialnumbers? */ nuclear@1: if(_lookup_serialno(endserial,currentno_list,currentnos)){ nuclear@1: nuclear@1: /* last page is in the starting serialno list, so we've bisected nuclear@1: down to (or just started with) a single link. Now we need to nuclear@1: find the last vorbis page belonging to the first vorbis stream nuclear@1: for this link. */ nuclear@1: nuclear@1: while(endserial != serialno){ nuclear@1: endserial = serialno; nuclear@1: vf->offset=_get_prev_page_serial(vf,currentno_list,currentnos,&endserial,&endgran); nuclear@1: } nuclear@1: nuclear@1: vf->links=m+1; nuclear@1: if(vf->offsets)_ogg_free(vf->offsets); nuclear@1: if(vf->serialnos)_ogg_free(vf->serialnos); nuclear@1: if(vf->dataoffsets)_ogg_free(vf->dataoffsets); nuclear@1: nuclear@1: vf->offsets=_ogg_malloc((vf->links+1)*sizeof(*vf->offsets)); nuclear@1: vf->vi=_ogg_realloc(vf->vi,vf->links*sizeof(*vf->vi)); nuclear@1: vf->vc=_ogg_realloc(vf->vc,vf->links*sizeof(*vf->vc)); nuclear@1: vf->serialnos=_ogg_malloc(vf->links*sizeof(*vf->serialnos)); nuclear@1: vf->dataoffsets=_ogg_malloc(vf->links*sizeof(*vf->dataoffsets)); nuclear@1: vf->pcmlengths=_ogg_malloc(vf->links*2*sizeof(*vf->pcmlengths)); nuclear@1: nuclear@1: vf->offsets[m+1]=end; nuclear@1: vf->offsets[m]=begin; nuclear@1: vf->pcmlengths[m*2+1]=(endgran<0?0:endgran); nuclear@1: nuclear@1: }else{ nuclear@1: nuclear@1: long *next_serialno_list=NULL; nuclear@1: int next_serialnos=0; nuclear@1: vorbis_info vi; nuclear@1: vorbis_comment vc; nuclear@1: nuclear@1: /* the below guards against garbage seperating the last and nuclear@1: first pages of two links. */ nuclear@1: while(searchedoffset){ nuclear@1: ret=_seek_helper(vf,bisect); nuclear@1: if(ret)return(ret); nuclear@1: } nuclear@1: nuclear@1: last=_get_next_page(vf,&og,-1); nuclear@1: if(last==OV_EREAD)return(OV_EREAD); nuclear@1: if(last<0 || !_lookup_page_serialno(&og,currentno_list,currentnos)){ nuclear@1: endsearched=bisect; nuclear@1: if(last>=0)next=last; nuclear@1: }else{ nuclear@1: searched=vf->offset; nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: /* Bisection point found */ nuclear@1: nuclear@1: /* for the time being, fetch end PCM offset the simple way */ nuclear@1: { nuclear@1: int testserial = serialno+1; nuclear@1: vf->offset = next; nuclear@1: while(testserial != serialno){ nuclear@1: testserial = serialno; nuclear@1: vf->offset=_get_prev_page_serial(vf,currentno_list,currentnos,&testserial,&searchgran); nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: if(vf->offset!=next){ nuclear@1: ret=_seek_helper(vf,next); nuclear@1: if(ret)return(ret); nuclear@1: } nuclear@1: nuclear@1: ret=_fetch_headers(vf,&vi,&vc,&next_serialno_list,&next_serialnos,NULL); nuclear@1: if(ret)return(ret); nuclear@1: serialno = vf->os.serialno; nuclear@1: dataoffset = vf->offset; nuclear@1: nuclear@1: /* this will consume a page, however the next bistection always nuclear@1: starts with a raw seek */ nuclear@1: pcmoffset = _initial_pcmoffset(vf,&vi); nuclear@1: nuclear@1: ret=_bisect_forward_serialno(vf,next,vf->offset,end,endgran,endserial, nuclear@1: next_serialno_list,next_serialnos,m+1); nuclear@1: if(ret)return(ret); nuclear@1: nuclear@1: if(next_serialno_list)_ogg_free(next_serialno_list); nuclear@1: nuclear@1: vf->offsets[m+1]=next; nuclear@1: vf->serialnos[m+1]=serialno; nuclear@1: vf->dataoffsets[m+1]=dataoffset; nuclear@1: nuclear@1: vf->vi[m+1]=vi; nuclear@1: vf->vc[m+1]=vc; nuclear@1: nuclear@1: vf->pcmlengths[m*2+1]=searchgran; nuclear@1: vf->pcmlengths[m*2+2]=pcmoffset; nuclear@1: vf->pcmlengths[m*2+3]-=pcmoffset; nuclear@1: if(vf->pcmlengths[m*2+3]<0)vf->pcmlengths[m*2+3]=0; nuclear@1: } nuclear@1: return(0); nuclear@1: } nuclear@1: nuclear@1: static int _make_decode_ready(OggVorbis_File *vf){ nuclear@1: if(vf->ready_state>STREAMSET)return 0; nuclear@1: if(vf->ready_stateseekable){ nuclear@1: if(vorbis_synthesis_init(&vf->vd,vf->vi+vf->current_link)) nuclear@1: return OV_EBADLINK; nuclear@1: }else{ nuclear@1: if(vorbis_synthesis_init(&vf->vd,vf->vi)) nuclear@1: return OV_EBADLINK; nuclear@1: } nuclear@1: vorbis_block_init(&vf->vd,&vf->vb); nuclear@1: vf->ready_state=INITSET; nuclear@1: vf->bittrack=0.f; nuclear@1: vf->samptrack=0.f; nuclear@1: return 0; nuclear@1: } nuclear@1: nuclear@1: static int _open_seekable2(OggVorbis_File *vf){ nuclear@1: ogg_int64_t dataoffset=vf->dataoffsets[0],end,endgran=-1; nuclear@1: int endserial=vf->os.serialno; nuclear@1: int serialno=vf->os.serialno; nuclear@1: nuclear@1: /* we're partially open and have a first link header state in nuclear@1: storage in vf */ nuclear@1: nuclear@1: /* fetch initial PCM offset */ nuclear@1: ogg_int64_t pcmoffset = _initial_pcmoffset(vf,vf->vi); nuclear@1: nuclear@1: /* we can seek, so set out learning all about this file */ nuclear@1: if(vf->callbacks.seek_func && vf->callbacks.tell_func){ nuclear@1: (vf->callbacks.seek_func)(vf->datasource,0,SEEK_END); nuclear@1: vf->offset=vf->end=(vf->callbacks.tell_func)(vf->datasource); nuclear@1: }else{ nuclear@1: vf->offset=vf->end=-1; nuclear@1: } nuclear@1: nuclear@1: /* If seek_func is implemented, tell_func must also be implemented */ nuclear@1: if(vf->end==-1) return(OV_EINVAL); nuclear@1: nuclear@1: /* Get the offset of the last page of the physical bitstream, or, if nuclear@1: we're lucky the last vorbis page of this link as most OggVorbis nuclear@1: files will contain a single logical bitstream */ nuclear@1: end=_get_prev_page_serial(vf,vf->serialnos+2,vf->serialnos[1],&endserial,&endgran); nuclear@1: if(end<0)return(end); nuclear@1: nuclear@1: /* now determine bitstream structure recursively */ nuclear@1: if(_bisect_forward_serialno(vf,0,dataoffset,vf->offset,endgran,endserial, nuclear@1: vf->serialnos+2,vf->serialnos[1],0)<0)return(OV_EREAD); nuclear@1: nuclear@1: vf->offsets[0]=0; nuclear@1: vf->serialnos[0]=serialno; nuclear@1: vf->dataoffsets[0]=dataoffset; nuclear@1: vf->pcmlengths[0]=pcmoffset; nuclear@1: vf->pcmlengths[1]-=pcmoffset; nuclear@1: if(vf->pcmlengths[1]<0)vf->pcmlengths[1]=0; nuclear@1: nuclear@1: return(ov_raw_seek(vf,dataoffset)); nuclear@1: } nuclear@1: nuclear@1: /* clear out the current logical bitstream decoder */ nuclear@1: static void _decode_clear(OggVorbis_File *vf){ nuclear@1: vorbis_dsp_clear(&vf->vd); nuclear@1: vorbis_block_clear(&vf->vb); nuclear@1: vf->ready_state=OPENED; nuclear@1: } nuclear@1: nuclear@1: /* fetch and process a packet. Handles the case where we're at a nuclear@1: bitstream boundary and dumps the decoding machine. If the decoding nuclear@1: machine is unloaded, it loads it. It also keeps pcm_offset up to nuclear@1: date (seek and read both use this. seek uses a special hack with nuclear@1: readp). nuclear@1: nuclear@1: return: <0) error, OV_HOLE (lost packet) or OV_EOF nuclear@1: 0) need more data (only if readp==0) nuclear@1: 1) got a packet nuclear@1: */ nuclear@1: nuclear@1: static int _fetch_and_process_packet(OggVorbis_File *vf, nuclear@1: ogg_packet *op_in, nuclear@1: int readp, nuclear@1: int spanp){ nuclear@1: ogg_page og; nuclear@1: nuclear@1: /* handle one packet. Try to fetch it from current stream state */ nuclear@1: /* extract packets from page */ nuclear@1: while(1){ nuclear@1: nuclear@1: if(vf->ready_state==STREAMSET){ nuclear@1: int ret=_make_decode_ready(vf); nuclear@1: if(ret<0)return ret; nuclear@1: } nuclear@1: nuclear@1: /* process a packet if we can. */ nuclear@1: nuclear@1: if(vf->ready_state==INITSET){ nuclear@1: int hs=vorbis_synthesis_halfrate_p(vf->vi); nuclear@1: nuclear@1: while(1) { nuclear@1: ogg_packet op; nuclear@1: ogg_packet *op_ptr=(op_in?op_in:&op); nuclear@1: int result=ogg_stream_packetout(&vf->os,op_ptr); nuclear@1: ogg_int64_t granulepos; nuclear@1: nuclear@1: op_in=NULL; nuclear@1: if(result==-1)return(OV_HOLE); /* hole in the data. */ nuclear@1: if(result>0){ nuclear@1: /* got a packet. process it */ nuclear@1: granulepos=op_ptr->granulepos; nuclear@1: if(!vorbis_synthesis(&vf->vb,op_ptr)){ /* lazy check for lazy nuclear@1: header handling. The nuclear@1: header packets aren't nuclear@1: audio, so if/when we nuclear@1: submit them, nuclear@1: vorbis_synthesis will nuclear@1: reject them */ nuclear@1: nuclear@1: /* suck in the synthesis data and track bitrate */ nuclear@1: { nuclear@1: int oldsamples=vorbis_synthesis_pcmout(&vf->vd,NULL); nuclear@1: /* for proper use of libvorbis within libvorbisfile, nuclear@1: oldsamples will always be zero. */ nuclear@1: if(oldsamples)return(OV_EFAULT); nuclear@1: nuclear@1: vorbis_synthesis_blockin(&vf->vd,&vf->vb); nuclear@1: vf->samptrack+=(vorbis_synthesis_pcmout(&vf->vd,NULL)<bittrack+=op_ptr->bytes*8; nuclear@1: } nuclear@1: nuclear@1: /* update the pcm offset. */ nuclear@1: if(granulepos!=-1 && !op_ptr->e_o_s){ nuclear@1: int link=(vf->seekable?vf->current_link:0); nuclear@1: int i,samples; nuclear@1: nuclear@1: /* this packet has a pcm_offset on it (the last packet nuclear@1: completed on a page carries the offset) After processing nuclear@1: (above), we know the pcm position of the *last* sample nuclear@1: ready to be returned. Find the offset of the *first* nuclear@1: nuclear@1: As an aside, this trick is inaccurate if we begin nuclear@1: reading anew right at the last page; the end-of-stream nuclear@1: granulepos declares the last frame in the stream, and the nuclear@1: last packet of the last page may be a partial frame. nuclear@1: So, we need a previous granulepos from an in-sequence page nuclear@1: to have a reference point. Thus the !op_ptr->e_o_s clause nuclear@1: above */ nuclear@1: nuclear@1: if(vf->seekable && link>0) nuclear@1: granulepos-=vf->pcmlengths[link*2]; nuclear@1: if(granulepos<0)granulepos=0; /* actually, this nuclear@1: shouldn't be possible nuclear@1: here unless the stream nuclear@1: is very broken */ nuclear@1: nuclear@1: samples=(vorbis_synthesis_pcmout(&vf->vd,NULL)<pcmlengths[i*2+1]; nuclear@1: vf->pcm_offset=granulepos; nuclear@1: } nuclear@1: return(1); nuclear@1: } nuclear@1: } nuclear@1: else nuclear@1: break; nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: if(vf->ready_state>=OPENED){ nuclear@1: ogg_int64_t ret; nuclear@1: nuclear@1: while(1){ nuclear@1: /* the loop is not strictly necessary, but there's no sense in nuclear@1: doing the extra checks of the larger loop for the common nuclear@1: case in a multiplexed bistream where the page is simply nuclear@1: part of a different logical bitstream; keep reading until nuclear@1: we get one with the correct serialno */ nuclear@1: nuclear@1: if(!readp)return(0); nuclear@1: if((ret=_get_next_page(vf,&og,-1))<0){ nuclear@1: return(OV_EOF); /* eof. leave unitialized */ nuclear@1: } nuclear@1: nuclear@1: /* bitrate tracking; add the header's bytes here, the body bytes nuclear@1: are done by packet above */ nuclear@1: vf->bittrack+=og.header_len*8; nuclear@1: nuclear@1: if(vf->ready_state==INITSET){ nuclear@1: if(vf->current_serialno!=ogg_page_serialno(&og)){ nuclear@1: nuclear@1: /* two possibilities: nuclear@1: 1) our decoding just traversed a bitstream boundary nuclear@1: 2) another stream is multiplexed into this logical section */ nuclear@1: nuclear@1: if(ogg_page_bos(&og)){ nuclear@1: /* boundary case */ nuclear@1: if(!spanp) nuclear@1: return(OV_EOF); nuclear@1: nuclear@1: _decode_clear(vf); nuclear@1: nuclear@1: if(!vf->seekable){ nuclear@1: vorbis_info_clear(vf->vi); nuclear@1: vorbis_comment_clear(vf->vc); nuclear@1: } nuclear@1: break; nuclear@1: nuclear@1: }else nuclear@1: continue; /* possibility #2 */ nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: break; nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: /* Do we need to load a new machine before submitting the page? */ nuclear@1: /* This is different in the seekable and non-seekable cases. nuclear@1: nuclear@1: In the seekable case, we already have all the header nuclear@1: information loaded and cached; we just initialize the machine nuclear@1: with it and continue on our merry way. nuclear@1: nuclear@1: In the non-seekable (streaming) case, we'll only be at a nuclear@1: boundary if we just left the previous logical bitstream and nuclear@1: we're now nominally at the header of the next bitstream nuclear@1: */ nuclear@1: nuclear@1: if(vf->ready_state!=INITSET){ nuclear@1: int link; nuclear@1: nuclear@1: if(vf->ready_stateseekable){ nuclear@1: long serialno = ogg_page_serialno(&og); nuclear@1: nuclear@1: /* match the serialno to bitstream section. We use this rather than nuclear@1: offset positions to avoid problems near logical bitstream nuclear@1: boundaries */ nuclear@1: nuclear@1: for(link=0;linklinks;link++) nuclear@1: if(vf->serialnos[link]==serialno)break; nuclear@1: nuclear@1: if(link==vf->links) continue; /* not the desired Vorbis nuclear@1: bitstream section; keep nuclear@1: trying */ nuclear@1: nuclear@1: vf->current_serialno=serialno; nuclear@1: vf->current_link=link; nuclear@1: nuclear@1: ogg_stream_reset_serialno(&vf->os,vf->current_serialno); nuclear@1: vf->ready_state=STREAMSET; nuclear@1: nuclear@1: }else{ nuclear@1: /* we're streaming */ nuclear@1: /* fetch the three header packets, build the info struct */ nuclear@1: nuclear@1: int ret=_fetch_headers(vf,vf->vi,vf->vc,NULL,NULL,&og); nuclear@1: if(ret)return(ret); nuclear@1: vf->current_serialno=vf->os.serialno; nuclear@1: vf->current_link++; nuclear@1: link=0; nuclear@1: } nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: /* the buffered page is the data we want, and we're ready for it; nuclear@1: add it to the stream state */ nuclear@1: ogg_stream_pagein(&vf->os,&og); nuclear@1: nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: /* if, eg, 64 bit stdio is configured by default, this will build with nuclear@1: fseek64 */ nuclear@1: static int _fseek64_wrap(FILE *f,ogg_int64_t off,int whence){ nuclear@1: if(f==NULL)return(-1); nuclear@1: return fseek(f,off,whence); nuclear@1: } nuclear@1: nuclear@1: static int _ov_open1(void *f,OggVorbis_File *vf,const char *initial, nuclear@1: long ibytes, ov_callbacks callbacks){ nuclear@1: int offsettest=((f && callbacks.seek_func)?callbacks.seek_func(f,0,SEEK_CUR):-1); nuclear@1: long *serialno_list=NULL; nuclear@1: int serialno_list_size=0; nuclear@1: int ret; nuclear@1: nuclear@1: memset(vf,0,sizeof(*vf)); nuclear@1: vf->datasource=f; nuclear@1: vf->callbacks = callbacks; nuclear@1: nuclear@1: /* init the framing state */ nuclear@1: ogg_sync_init(&vf->oy); nuclear@1: nuclear@1: /* perhaps some data was previously read into a buffer for testing nuclear@1: against other stream types. Allow initialization from this nuclear@1: previously read data (especially as we may be reading from a nuclear@1: non-seekable stream) */ nuclear@1: if(initial){ nuclear@1: char *buffer=ogg_sync_buffer(&vf->oy,ibytes); nuclear@1: memcpy(buffer,initial,ibytes); nuclear@1: ogg_sync_wrote(&vf->oy,ibytes); nuclear@1: } nuclear@1: nuclear@1: /* can we seek? Stevens suggests the seek test was portable */ nuclear@1: if(offsettest!=-1)vf->seekable=1; nuclear@1: nuclear@1: /* No seeking yet; Set up a 'single' (current) logical bitstream nuclear@1: entry for partial open */ nuclear@1: vf->links=1; nuclear@1: vf->vi=_ogg_calloc(vf->links,sizeof(*vf->vi)); nuclear@1: vf->vc=_ogg_calloc(vf->links,sizeof(*vf->vc)); nuclear@1: ogg_stream_init(&vf->os,-1); /* fill in the serialno later */ nuclear@1: nuclear@1: /* Fetch all BOS pages, store the vorbis header and all seen serial nuclear@1: numbers, load subsequent vorbis setup headers */ nuclear@1: if((ret=_fetch_headers(vf,vf->vi,vf->vc,&serialno_list,&serialno_list_size,NULL))<0){ nuclear@1: vf->datasource=NULL; nuclear@1: ov_clear(vf); nuclear@1: }else{ nuclear@1: /* serial number list for first link needs to be held somewhere nuclear@1: for second stage of seekable stream open; this saves having to nuclear@1: seek/reread first link's serialnumber data then. */ nuclear@1: vf->serialnos=_ogg_calloc(serialno_list_size+2,sizeof(*vf->serialnos)); nuclear@1: vf->serialnos[0]=vf->current_serialno=vf->os.serialno; nuclear@1: vf->serialnos[1]=serialno_list_size; nuclear@1: memcpy(vf->serialnos+2,serialno_list,serialno_list_size*sizeof(*vf->serialnos)); nuclear@1: nuclear@1: vf->offsets=_ogg_calloc(1,sizeof(*vf->offsets)); nuclear@1: vf->dataoffsets=_ogg_calloc(1,sizeof(*vf->dataoffsets)); nuclear@1: vf->offsets[0]=0; nuclear@1: vf->dataoffsets[0]=vf->offset; nuclear@1: nuclear@1: vf->ready_state=PARTOPEN; nuclear@1: } nuclear@1: if(serialno_list)_ogg_free(serialno_list); nuclear@1: return(ret); nuclear@1: } nuclear@1: nuclear@1: static int _ov_open2(OggVorbis_File *vf){ nuclear@1: if(vf->ready_state != PARTOPEN) return OV_EINVAL; nuclear@1: vf->ready_state=OPENED; nuclear@1: if(vf->seekable){ nuclear@1: int ret=_open_seekable2(vf); nuclear@1: if(ret){ nuclear@1: vf->datasource=NULL; nuclear@1: ov_clear(vf); nuclear@1: } nuclear@1: return(ret); nuclear@1: }else nuclear@1: vf->ready_state=STREAMSET; nuclear@1: nuclear@1: return 0; nuclear@1: } nuclear@1: nuclear@1: nuclear@1: /* clear out the OggVorbis_File struct */ nuclear@1: int ov_clear(OggVorbis_File *vf){ nuclear@1: if(vf){ nuclear@1: vorbis_block_clear(&vf->vb); nuclear@1: vorbis_dsp_clear(&vf->vd); nuclear@1: ogg_stream_clear(&vf->os); nuclear@1: nuclear@1: if(vf->vi && vf->links){ nuclear@1: int i; nuclear@1: for(i=0;ilinks;i++){ nuclear@1: vorbis_info_clear(vf->vi+i); nuclear@1: vorbis_comment_clear(vf->vc+i); nuclear@1: } nuclear@1: _ogg_free(vf->vi); nuclear@1: _ogg_free(vf->vc); nuclear@1: } nuclear@1: if(vf->dataoffsets)_ogg_free(vf->dataoffsets); nuclear@1: if(vf->pcmlengths)_ogg_free(vf->pcmlengths); nuclear@1: if(vf->serialnos)_ogg_free(vf->serialnos); nuclear@1: if(vf->offsets)_ogg_free(vf->offsets); nuclear@1: ogg_sync_clear(&vf->oy); nuclear@1: if(vf->datasource && vf->callbacks.close_func) nuclear@1: (vf->callbacks.close_func)(vf->datasource); nuclear@1: memset(vf,0,sizeof(*vf)); nuclear@1: } nuclear@1: #ifdef DEBUG_LEAKS nuclear@1: _VDBG_dump(); nuclear@1: #endif nuclear@1: return(0); nuclear@1: } nuclear@1: nuclear@1: /* inspects the OggVorbis file and finds/documents all the logical nuclear@1: bitstreams contained in it. Tries to be tolerant of logical nuclear@1: bitstream sections that are truncated/woogie. nuclear@1: nuclear@1: return: -1) error nuclear@1: 0) OK nuclear@1: */ nuclear@1: nuclear@1: int ov_open_callbacks(void *f,OggVorbis_File *vf, nuclear@1: const char *initial,long ibytes,ov_callbacks callbacks){ nuclear@1: int ret=_ov_open1(f,vf,initial,ibytes,callbacks); nuclear@1: if(ret)return ret; nuclear@1: return _ov_open2(vf); nuclear@1: } nuclear@1: nuclear@1: int ov_open(FILE *f,OggVorbis_File *vf,const char *initial,long ibytes){ nuclear@1: ov_callbacks callbacks = { nuclear@1: (size_t (*)(void *, size_t, size_t, void *)) fread, nuclear@1: (int (*)(void *, ogg_int64_t, int)) _fseek64_wrap, nuclear@1: (int (*)(void *)) fclose, nuclear@1: (long (*)(void *)) ftell nuclear@1: }; nuclear@1: nuclear@1: return ov_open_callbacks((void *)f, vf, initial, ibytes, callbacks); nuclear@1: } nuclear@1: nuclear@1: int ov_fopen(const char *path,OggVorbis_File *vf){ nuclear@1: int ret; nuclear@1: FILE *f = fopen(path,"rb"); nuclear@1: if(!f) return -1; nuclear@1: nuclear@1: ret = ov_open(f,vf,NULL,0); nuclear@1: if(ret) fclose(f); nuclear@1: return ret; nuclear@1: } nuclear@1: nuclear@1: nuclear@1: /* cheap hack for game usage where downsampling is desirable; there's nuclear@1: no need for SRC as we can just do it cheaply in libvorbis. */ nuclear@1: nuclear@1: int ov_halfrate(OggVorbis_File *vf,int flag){ nuclear@1: int i; nuclear@1: if(vf->vi==NULL)return OV_EINVAL; nuclear@1: if(vf->ready_state>STREAMSET){ nuclear@1: /* clear out stream state; dumping the decode machine is needed to nuclear@1: reinit the MDCT lookups. */ nuclear@1: vorbis_dsp_clear(&vf->vd); nuclear@1: vorbis_block_clear(&vf->vb); nuclear@1: vf->ready_state=STREAMSET; nuclear@1: if(vf->pcm_offset>=0){ nuclear@1: ogg_int64_t pos=vf->pcm_offset; nuclear@1: vf->pcm_offset=-1; /* make sure the pos is dumped if unseekable */ nuclear@1: ov_pcm_seek(vf,pos); nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: for(i=0;ilinks;i++){ nuclear@1: if(vorbis_synthesis_halfrate(vf->vi+i,flag)){ nuclear@1: if(flag) ov_halfrate(vf,0); nuclear@1: return OV_EINVAL; nuclear@1: } nuclear@1: } nuclear@1: return 0; nuclear@1: } nuclear@1: nuclear@1: int ov_halfrate_p(OggVorbis_File *vf){ nuclear@1: if(vf->vi==NULL)return OV_EINVAL; nuclear@1: return vorbis_synthesis_halfrate_p(vf->vi); nuclear@1: } nuclear@1: nuclear@1: /* Only partially open the vorbis file; test for Vorbisness, and load nuclear@1: the headers for the first chain. Do not seek (although test for nuclear@1: seekability). Use ov_test_open to finish opening the file, else nuclear@1: ov_clear to close/free it. Same return codes as open. */ nuclear@1: nuclear@1: int ov_test_callbacks(void *f,OggVorbis_File *vf, nuclear@1: const char *initial,long ibytes,ov_callbacks callbacks) nuclear@1: { nuclear@1: return _ov_open1(f,vf,initial,ibytes,callbacks); nuclear@1: } nuclear@1: nuclear@1: int ov_test(FILE *f,OggVorbis_File *vf,const char *initial,long ibytes){ nuclear@1: ov_callbacks callbacks = { nuclear@1: (size_t (*)(void *, size_t, size_t, void *)) fread, nuclear@1: (int (*)(void *, ogg_int64_t, int)) _fseek64_wrap, nuclear@1: (int (*)(void *)) fclose, nuclear@1: (long (*)(void *)) ftell nuclear@1: }; nuclear@1: nuclear@1: return ov_test_callbacks((void *)f, vf, initial, ibytes, callbacks); nuclear@1: } nuclear@1: nuclear@1: int ov_test_open(OggVorbis_File *vf){ nuclear@1: if(vf->ready_state!=PARTOPEN)return(OV_EINVAL); nuclear@1: return _ov_open2(vf); nuclear@1: } nuclear@1: nuclear@1: /* How many logical bitstreams in this physical bitstream? */ nuclear@1: long ov_streams(OggVorbis_File *vf){ nuclear@1: return vf->links; nuclear@1: } nuclear@1: nuclear@1: /* Is the FILE * associated with vf seekable? */ nuclear@1: long ov_seekable(OggVorbis_File *vf){ nuclear@1: return vf->seekable; nuclear@1: } nuclear@1: nuclear@1: /* returns the bitrate for a given logical bitstream or the entire nuclear@1: physical bitstream. If the file is open for random access, it will nuclear@1: find the *actual* average bitrate. If the file is streaming, it nuclear@1: returns the nominal bitrate (if set) else the average of the nuclear@1: upper/lower bounds (if set) else -1 (unset). nuclear@1: nuclear@1: If you want the actual bitrate field settings, get them from the nuclear@1: vorbis_info structs */ nuclear@1: nuclear@1: long ov_bitrate(OggVorbis_File *vf,int i){ nuclear@1: if(vf->ready_state=vf->links)return(OV_EINVAL); nuclear@1: if(!vf->seekable && i!=0)return(ov_bitrate(vf,0)); nuclear@1: if(i<0){ nuclear@1: ogg_int64_t bits=0; nuclear@1: int i; nuclear@1: float br; nuclear@1: for(i=0;ilinks;i++) nuclear@1: bits+=(vf->offsets[i+1]-vf->dataoffsets[i])*8; nuclear@1: /* This once read: return(rint(bits/ov_time_total(vf,-1))); nuclear@1: * gcc 3.x on x86 miscompiled this at optimisation level 2 and above, nuclear@1: * so this is slightly transformed to make it work. nuclear@1: */ nuclear@1: br = bits/ov_time_total(vf,-1); nuclear@1: return(rint(br)); nuclear@1: }else{ nuclear@1: if(vf->seekable){ nuclear@1: /* return the actual bitrate */ nuclear@1: return(rint((vf->offsets[i+1]-vf->dataoffsets[i])*8/ov_time_total(vf,i))); nuclear@1: }else{ nuclear@1: /* return nominal if set */ nuclear@1: if(vf->vi[i].bitrate_nominal>0){ nuclear@1: return vf->vi[i].bitrate_nominal; nuclear@1: }else{ nuclear@1: if(vf->vi[i].bitrate_upper>0){ nuclear@1: if(vf->vi[i].bitrate_lower>0){ nuclear@1: return (vf->vi[i].bitrate_upper+vf->vi[i].bitrate_lower)/2; nuclear@1: }else{ nuclear@1: return vf->vi[i].bitrate_upper; nuclear@1: } nuclear@1: } nuclear@1: return(OV_FALSE); nuclear@1: } nuclear@1: } nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: /* returns the actual bitrate since last call. returns -1 if no nuclear@1: additional data to offer since last call (or at beginning of stream), nuclear@1: EINVAL if stream is only partially open nuclear@1: */ nuclear@1: long ov_bitrate_instant(OggVorbis_File *vf){ nuclear@1: int link=(vf->seekable?vf->current_link:0); nuclear@1: long ret; nuclear@1: if(vf->ready_statesamptrack==0)return(OV_FALSE); nuclear@1: ret=vf->bittrack/vf->samptrack*vf->vi[link].rate+.5; nuclear@1: vf->bittrack=0.f; nuclear@1: vf->samptrack=0.f; nuclear@1: return(ret); nuclear@1: } nuclear@1: nuclear@1: /* Guess */ nuclear@1: long ov_serialnumber(OggVorbis_File *vf,int i){ nuclear@1: if(i>=vf->links)return(ov_serialnumber(vf,vf->links-1)); nuclear@1: if(!vf->seekable && i>=0)return(ov_serialnumber(vf,-1)); nuclear@1: if(i<0){ nuclear@1: return(vf->current_serialno); nuclear@1: }else{ nuclear@1: return(vf->serialnos[i]); nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: /* returns: total raw (compressed) length of content if i==-1 nuclear@1: raw (compressed) length of that logical bitstream for i==0 to n nuclear@1: OV_EINVAL if the stream is not seekable (we can't know the length) nuclear@1: or if stream is only partially open nuclear@1: */ nuclear@1: ogg_int64_t ov_raw_total(OggVorbis_File *vf,int i){ nuclear@1: if(vf->ready_stateseekable || i>=vf->links)return(OV_EINVAL); nuclear@1: if(i<0){ nuclear@1: ogg_int64_t acc=0; nuclear@1: int i; nuclear@1: for(i=0;ilinks;i++) nuclear@1: acc+=ov_raw_total(vf,i); nuclear@1: return(acc); nuclear@1: }else{ nuclear@1: return(vf->offsets[i+1]-vf->offsets[i]); nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: /* returns: total PCM length (samples) of content if i==-1 PCM length nuclear@1: (samples) of that logical bitstream for i==0 to n nuclear@1: OV_EINVAL if the stream is not seekable (we can't know the nuclear@1: length) or only partially open nuclear@1: */ nuclear@1: ogg_int64_t ov_pcm_total(OggVorbis_File *vf,int i){ nuclear@1: if(vf->ready_stateseekable || i>=vf->links)return(OV_EINVAL); nuclear@1: if(i<0){ nuclear@1: ogg_int64_t acc=0; nuclear@1: int i; nuclear@1: for(i=0;ilinks;i++) nuclear@1: acc+=ov_pcm_total(vf,i); nuclear@1: return(acc); nuclear@1: }else{ nuclear@1: return(vf->pcmlengths[i*2+1]); nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: /* returns: total seconds of content if i==-1 nuclear@1: seconds in that logical bitstream for i==0 to n nuclear@1: OV_EINVAL if the stream is not seekable (we can't know the nuclear@1: length) or only partially open nuclear@1: */ nuclear@1: double ov_time_total(OggVorbis_File *vf,int i){ nuclear@1: if(vf->ready_stateseekable || i>=vf->links)return(OV_EINVAL); nuclear@1: if(i<0){ nuclear@1: double acc=0; nuclear@1: int i; nuclear@1: for(i=0;ilinks;i++) nuclear@1: acc+=ov_time_total(vf,i); nuclear@1: return(acc); nuclear@1: }else{ nuclear@1: return((double)(vf->pcmlengths[i*2+1])/vf->vi[i].rate); nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: /* seek to an offset relative to the *compressed* data. This also nuclear@1: scans packets to update the PCM cursor. It will cross a logical nuclear@1: bitstream boundary, but only if it can't get any packets out of the nuclear@1: tail of the bitstream we seek to (so no surprises). nuclear@1: nuclear@1: returns zero on success, nonzero on failure */ nuclear@1: nuclear@1: int ov_raw_seek(OggVorbis_File *vf,ogg_int64_t pos){ nuclear@1: ogg_stream_state work_os; nuclear@1: int ret; nuclear@1: nuclear@1: if(vf->ready_stateseekable) nuclear@1: return(OV_ENOSEEK); /* don't dump machine if we can't seek */ nuclear@1: nuclear@1: if(pos<0 || pos>vf->end)return(OV_EINVAL); nuclear@1: nuclear@1: /* is the seek position outside our current link [if any]? */ nuclear@1: if(vf->ready_state>=STREAMSET){ nuclear@1: if(posoffsets[vf->current_link] || pos>=vf->offsets[vf->current_link+1]) nuclear@1: _decode_clear(vf); /* clear out stream state */ nuclear@1: } nuclear@1: nuclear@1: /* don't yet clear out decoding machine (if it's initialized), in nuclear@1: the case we're in the same link. Restart the decode lapping, and nuclear@1: let _fetch_and_process_packet deal with a potential bitstream nuclear@1: boundary */ nuclear@1: vf->pcm_offset=-1; nuclear@1: ogg_stream_reset_serialno(&vf->os, nuclear@1: vf->current_serialno); /* must set serialno */ nuclear@1: vorbis_synthesis_restart(&vf->vd); nuclear@1: nuclear@1: ret=_seek_helper(vf,pos); nuclear@1: if(ret)goto seek_error; nuclear@1: nuclear@1: /* we need to make sure the pcm_offset is set, but we don't want to nuclear@1: advance the raw cursor past good packets just to get to the first nuclear@1: with a granulepos. That's not equivalent behavior to beginning nuclear@1: decoding as immediately after the seek position as possible. nuclear@1: nuclear@1: So, a hack. We use two stream states; a local scratch state and nuclear@1: the shared vf->os stream state. We use the local state to nuclear@1: scan, and the shared state as a buffer for later decode. nuclear@1: nuclear@1: Unfortuantely, on the last page we still advance to last packet nuclear@1: because the granulepos on the last page is not necessarily on a nuclear@1: packet boundary, and we need to make sure the granpos is nuclear@1: correct. nuclear@1: */ nuclear@1: nuclear@1: { nuclear@1: ogg_page og; nuclear@1: ogg_packet op; nuclear@1: int lastblock=0; nuclear@1: int accblock=0; nuclear@1: int thisblock=0; nuclear@1: int lastflag=0; nuclear@1: int firstflag=0; nuclear@1: ogg_int64_t pagepos=-1; nuclear@1: nuclear@1: ogg_stream_init(&work_os,vf->current_serialno); /* get the memory ready */ nuclear@1: ogg_stream_reset(&work_os); /* eliminate the spurious OV_HOLE nuclear@1: return from not necessarily nuclear@1: starting from the beginning */ nuclear@1: nuclear@1: while(1){ nuclear@1: if(vf->ready_state>=STREAMSET){ nuclear@1: /* snarf/scan a packet if we can */ nuclear@1: int result=ogg_stream_packetout(&work_os,&op); nuclear@1: nuclear@1: if(result>0){ nuclear@1: nuclear@1: if(vf->vi[vf->current_link].codec_setup){ nuclear@1: thisblock=vorbis_packet_blocksize(vf->vi+vf->current_link,&op); nuclear@1: if(thisblock<0){ nuclear@1: ogg_stream_packetout(&vf->os,NULL); nuclear@1: thisblock=0; nuclear@1: }else{ nuclear@1: nuclear@1: /* We can't get a guaranteed correct pcm position out of the nuclear@1: last page in a stream because it might have a 'short' nuclear@1: granpos, which can only be detected in the presence of a nuclear@1: preceding page. However, if the last page is also the first nuclear@1: page, the granpos rules of a first page take precedence. Not nuclear@1: only that, but for first==last, the EOS page must be treated nuclear@1: as if its a normal first page for the stream to open/play. */ nuclear@1: if(lastflag && !firstflag) nuclear@1: ogg_stream_packetout(&vf->os,NULL); nuclear@1: else nuclear@1: if(lastblock)accblock+=(lastblock+thisblock)>>2; nuclear@1: } nuclear@1: nuclear@1: if(op.granulepos!=-1){ nuclear@1: int i,link=vf->current_link; nuclear@1: ogg_int64_t granulepos=op.granulepos-vf->pcmlengths[link*2]; nuclear@1: if(granulepos<0)granulepos=0; nuclear@1: nuclear@1: for(i=0;ipcmlengths[i*2+1]; nuclear@1: vf->pcm_offset=granulepos-accblock; nuclear@1: if(vf->pcm_offset<0)vf->pcm_offset=0; nuclear@1: break; nuclear@1: } nuclear@1: lastblock=thisblock; nuclear@1: continue; nuclear@1: }else nuclear@1: ogg_stream_packetout(&vf->os,NULL); nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: if(!lastblock){ nuclear@1: pagepos=_get_next_page(vf,&og,-1); nuclear@1: if(pagepos<0){ nuclear@1: vf->pcm_offset=ov_pcm_total(vf,-1); nuclear@1: break; nuclear@1: } nuclear@1: }else{ nuclear@1: /* huh? Bogus stream with packets but no granulepos */ nuclear@1: vf->pcm_offset=-1; nuclear@1: break; nuclear@1: } nuclear@1: nuclear@1: /* has our decoding just traversed a bitstream boundary? */ nuclear@1: if(vf->ready_state>=STREAMSET){ nuclear@1: if(vf->current_serialno!=ogg_page_serialno(&og)){ nuclear@1: nuclear@1: /* two possibilities: nuclear@1: 1) our decoding just traversed a bitstream boundary nuclear@1: 2) another stream is multiplexed into this logical section? */ nuclear@1: nuclear@1: if(ogg_page_bos(&og)){ nuclear@1: /* we traversed */ nuclear@1: _decode_clear(vf); /* clear out stream state */ nuclear@1: ogg_stream_clear(&work_os); nuclear@1: } /* else, do nothing; next loop will scoop another page */ nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: if(vf->ready_statelinks;link++) nuclear@1: if(vf->serialnos[link]==serialno)break; nuclear@1: nuclear@1: if(link==vf->links) continue; /* not the desired Vorbis nuclear@1: bitstream section; keep nuclear@1: trying */ nuclear@1: vf->current_link=link; nuclear@1: vf->current_serialno=serialno; nuclear@1: ogg_stream_reset_serialno(&vf->os,serialno); nuclear@1: ogg_stream_reset_serialno(&work_os,serialno); nuclear@1: vf->ready_state=STREAMSET; nuclear@1: firstflag=(pagepos<=vf->dataoffsets[link]); nuclear@1: } nuclear@1: nuclear@1: ogg_stream_pagein(&vf->os,&og); nuclear@1: ogg_stream_pagein(&work_os,&og); nuclear@1: lastflag=ogg_page_eos(&og); nuclear@1: nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: ogg_stream_clear(&work_os); nuclear@1: vf->bittrack=0.f; nuclear@1: vf->samptrack=0.f; nuclear@1: return(0); nuclear@1: nuclear@1: seek_error: nuclear@1: /* dump the machine so we're in a known state */ nuclear@1: vf->pcm_offset=-1; nuclear@1: ogg_stream_clear(&work_os); nuclear@1: _decode_clear(vf); nuclear@1: return OV_EBADLINK; nuclear@1: } nuclear@1: nuclear@1: /* Page granularity seek (faster than sample granularity because we nuclear@1: don't do the last bit of decode to find a specific sample). nuclear@1: nuclear@1: Seek to the last [granule marked] page preceding the specified pos nuclear@1: location, such that decoding past the returned point will quickly nuclear@1: arrive at the requested position. */ nuclear@1: int ov_pcm_seek_page(OggVorbis_File *vf,ogg_int64_t pos){ nuclear@1: int link=-1; nuclear@1: ogg_int64_t result=0; nuclear@1: ogg_int64_t total=ov_pcm_total(vf,-1); nuclear@1: nuclear@1: if(vf->ready_stateseekable)return(OV_ENOSEEK); nuclear@1: nuclear@1: if(pos<0 || pos>total)return(OV_EINVAL); nuclear@1: nuclear@1: /* which bitstream section does this pcm offset occur in? */ nuclear@1: for(link=vf->links-1;link>=0;link--){ nuclear@1: total-=vf->pcmlengths[link*2+1]; nuclear@1: if(pos>=total)break; nuclear@1: } nuclear@1: nuclear@1: /* search within the logical bitstream for the page with the highest nuclear@1: pcm_pos preceding (or equal to) pos. There is a danger here; nuclear@1: missing pages or incorrect frame number information in the nuclear@1: bitstream could make our task impossible. Account for that (it nuclear@1: would be an error condition) */ nuclear@1: nuclear@1: /* new search algorithm by HB (Nicholas Vinen) */ nuclear@1: { nuclear@1: ogg_int64_t end=vf->offsets[link+1]; nuclear@1: ogg_int64_t begin=vf->offsets[link]; nuclear@1: ogg_int64_t begintime = vf->pcmlengths[link*2]; nuclear@1: ogg_int64_t endtime = vf->pcmlengths[link*2+1]+begintime; nuclear@1: ogg_int64_t target=pos-total+begintime; nuclear@1: ogg_int64_t best=begin; nuclear@1: nuclear@1: ogg_page og; nuclear@1: while(beginoffset){ nuclear@1: result=_seek_helper(vf,bisect); nuclear@1: if(result) goto seek_error; nuclear@1: } nuclear@1: nuclear@1: while(beginoffset); nuclear@1: if(result==OV_EREAD) goto seek_error; nuclear@1: if(result<0){ nuclear@1: if(bisect<=begin+1) nuclear@1: end=begin; /* found it */ nuclear@1: else{ nuclear@1: if(bisect==0) goto seek_error; nuclear@1: bisect-=CHUNKSIZE; nuclear@1: if(bisect<=begin)bisect=begin+1; nuclear@1: result=_seek_helper(vf,bisect); nuclear@1: if(result) goto seek_error; nuclear@1: } nuclear@1: }else{ nuclear@1: ogg_int64_t granulepos; nuclear@1: nuclear@1: if(ogg_page_serialno(&og)!=vf->serialnos[link]) nuclear@1: continue; nuclear@1: nuclear@1: granulepos=ogg_page_granulepos(&og); nuclear@1: if(granulepos==-1)continue; nuclear@1: nuclear@1: if(granuleposoffset; /* raw offset of next page */ nuclear@1: begintime=granulepos; nuclear@1: nuclear@1: if(target-begintime>44100)break; nuclear@1: bisect=begin; /* *not* begin + 1 */ nuclear@1: }else{ nuclear@1: if(bisect<=begin+1) nuclear@1: end=begin; /* found it */ nuclear@1: else{ nuclear@1: if(end==vf->offset){ /* we're pretty close - we'd be stuck in */ nuclear@1: end=result; nuclear@1: bisect-=CHUNKSIZE; /* an endless loop otherwise. */ nuclear@1: if(bisect<=begin)bisect=begin+1; nuclear@1: result=_seek_helper(vf,bisect); nuclear@1: if(result) goto seek_error; nuclear@1: }else{ nuclear@1: end=bisect; nuclear@1: endtime=granulepos; nuclear@1: break; nuclear@1: } nuclear@1: } nuclear@1: } nuclear@1: } nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: /* found our page. seek to it, update pcm offset. Easier case than nuclear@1: raw_seek, don't keep packets preceding granulepos. */ nuclear@1: { nuclear@1: ogg_page og; nuclear@1: ogg_packet op; nuclear@1: nuclear@1: /* seek */ nuclear@1: result=_seek_helper(vf,best); nuclear@1: vf->pcm_offset=-1; nuclear@1: if(result) goto seek_error; nuclear@1: result=_get_next_page(vf,&og,-1); nuclear@1: if(result<0) goto seek_error; nuclear@1: nuclear@1: if(link!=vf->current_link){ nuclear@1: /* Different link; dump entire decode machine */ nuclear@1: _decode_clear(vf); nuclear@1: nuclear@1: vf->current_link=link; nuclear@1: vf->current_serialno=vf->serialnos[link]; nuclear@1: vf->ready_state=STREAMSET; nuclear@1: nuclear@1: }else{ nuclear@1: vorbis_synthesis_restart(&vf->vd); nuclear@1: } nuclear@1: nuclear@1: ogg_stream_reset_serialno(&vf->os,vf->current_serialno); nuclear@1: ogg_stream_pagein(&vf->os,&og); nuclear@1: nuclear@1: /* pull out all but last packet; the one with granulepos */ nuclear@1: while(1){ nuclear@1: result=ogg_stream_packetpeek(&vf->os,&op); nuclear@1: if(result==0){ nuclear@1: /* !!! the packet finishing this page originated on a nuclear@1: preceding page. Keep fetching previous pages until we nuclear@1: get one with a granulepos or without the 'continued' flag nuclear@1: set. Then just use raw_seek for simplicity. */ nuclear@1: nuclear@1: result=_seek_helper(vf,best); nuclear@1: if(result<0) goto seek_error; nuclear@1: nuclear@1: while(1){ nuclear@1: result=_get_prev_page(vf,&og); nuclear@1: if(result<0) goto seek_error; nuclear@1: if(ogg_page_serialno(&og)==vf->current_serialno && nuclear@1: (ogg_page_granulepos(&og)>-1 || nuclear@1: !ogg_page_continued(&og))){ nuclear@1: return ov_raw_seek(vf,result); nuclear@1: } nuclear@1: vf->offset=result; nuclear@1: } nuclear@1: } nuclear@1: if(result<0){ nuclear@1: result = OV_EBADPACKET; nuclear@1: goto seek_error; nuclear@1: } nuclear@1: if(op.granulepos!=-1){ nuclear@1: vf->pcm_offset=op.granulepos-vf->pcmlengths[vf->current_link*2]; nuclear@1: if(vf->pcm_offset<0)vf->pcm_offset=0; nuclear@1: vf->pcm_offset+=total; nuclear@1: break; nuclear@1: }else nuclear@1: result=ogg_stream_packetout(&vf->os,NULL); nuclear@1: } nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: /* verify result */ nuclear@1: if(vf->pcm_offset>pos || pos>ov_pcm_total(vf,-1)){ nuclear@1: result=OV_EFAULT; nuclear@1: goto seek_error; nuclear@1: } nuclear@1: vf->bittrack=0.f; nuclear@1: vf->samptrack=0.f; nuclear@1: return(0); nuclear@1: nuclear@1: seek_error: nuclear@1: /* dump machine so we're in a known state */ nuclear@1: vf->pcm_offset=-1; nuclear@1: _decode_clear(vf); nuclear@1: return (int)result; nuclear@1: } nuclear@1: nuclear@1: /* seek to a sample offset relative to the decompressed pcm stream nuclear@1: returns zero on success, nonzero on failure */ nuclear@1: nuclear@1: int ov_pcm_seek(OggVorbis_File *vf,ogg_int64_t pos){ nuclear@1: int thisblock,lastblock=0; nuclear@1: int ret=ov_pcm_seek_page(vf,pos); nuclear@1: if(ret<0)return(ret); nuclear@1: if((ret=_make_decode_ready(vf)))return ret; nuclear@1: nuclear@1: /* discard leading packets we don't need for the lapping of the nuclear@1: position we want; don't decode them */ nuclear@1: nuclear@1: while(1){ nuclear@1: ogg_packet op; nuclear@1: ogg_page og; nuclear@1: nuclear@1: int ret=ogg_stream_packetpeek(&vf->os,&op); nuclear@1: if(ret>0){ nuclear@1: thisblock=vorbis_packet_blocksize(vf->vi+vf->current_link,&op); nuclear@1: if(thisblock<0){ nuclear@1: ogg_stream_packetout(&vf->os,NULL); nuclear@1: continue; /* non audio packet */ nuclear@1: } nuclear@1: if(lastblock)vf->pcm_offset+=(lastblock+thisblock)>>2; nuclear@1: nuclear@1: if(vf->pcm_offset+((thisblock+ nuclear@1: vorbis_info_blocksize(vf->vi,1))>>2)>=pos)break; nuclear@1: nuclear@1: /* remove the packet from packet queue and track its granulepos */ nuclear@1: ogg_stream_packetout(&vf->os,NULL); nuclear@1: vorbis_synthesis_trackonly(&vf->vb,&op); /* set up a vb with nuclear@1: only tracking, no nuclear@1: pcm_decode */ nuclear@1: vorbis_synthesis_blockin(&vf->vd,&vf->vb); nuclear@1: nuclear@1: /* end of logical stream case is hard, especially with exact nuclear@1: length positioning. */ nuclear@1: nuclear@1: if(op.granulepos>-1){ nuclear@1: int i; nuclear@1: /* always believe the stream markers */ nuclear@1: vf->pcm_offset=op.granulepos-vf->pcmlengths[vf->current_link*2]; nuclear@1: if(vf->pcm_offset<0)vf->pcm_offset=0; nuclear@1: for(i=0;icurrent_link;i++) nuclear@1: vf->pcm_offset+=vf->pcmlengths[i*2+1]; nuclear@1: } nuclear@1: nuclear@1: lastblock=thisblock; nuclear@1: nuclear@1: }else{ nuclear@1: if(ret<0 && ret!=OV_HOLE)break; nuclear@1: nuclear@1: /* suck in a new page */ nuclear@1: if(_get_next_page(vf,&og,-1)<0)break; nuclear@1: if(ogg_page_bos(&og))_decode_clear(vf); nuclear@1: nuclear@1: if(vf->ready_statelinks;link++) nuclear@1: if(vf->serialnos[link]==serialno)break; nuclear@1: if(link==vf->links) continue; nuclear@1: vf->current_link=link; nuclear@1: nuclear@1: vf->ready_state=STREAMSET; nuclear@1: vf->current_serialno=ogg_page_serialno(&og); nuclear@1: ogg_stream_reset_serialno(&vf->os,serialno); nuclear@1: ret=_make_decode_ready(vf); nuclear@1: if(ret)return ret; nuclear@1: lastblock=0; nuclear@1: } nuclear@1: nuclear@1: ogg_stream_pagein(&vf->os,&og); nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: vf->bittrack=0.f; nuclear@1: vf->samptrack=0.f; nuclear@1: /* discard samples until we reach the desired position. Crossing a nuclear@1: logical bitstream boundary with abandon is OK. */ nuclear@1: { nuclear@1: /* note that halfrate could be set differently in each link, but nuclear@1: vorbisfile encoforces all links are set or unset */ nuclear@1: int hs=vorbis_synthesis_halfrate_p(vf->vi); nuclear@1: while(vf->pcm_offset<((pos>>hs)<pcm_offset)>>hs; nuclear@1: long samples=vorbis_synthesis_pcmout(&vf->vd,NULL); nuclear@1: nuclear@1: if(samples>target)samples=target; nuclear@1: vorbis_synthesis_read(&vf->vd,samples); nuclear@1: vf->pcm_offset+=samples<pcm_offset=ov_pcm_total(vf,-1); /* eof */ nuclear@1: } nuclear@1: } nuclear@1: return 0; nuclear@1: } nuclear@1: nuclear@1: /* seek to a playback time relative to the decompressed pcm stream nuclear@1: returns zero on success, nonzero on failure */ nuclear@1: int ov_time_seek(OggVorbis_File *vf,double seconds){ nuclear@1: /* translate time to PCM position and call ov_pcm_seek */ nuclear@1: nuclear@1: int link=-1; nuclear@1: ogg_int64_t pcm_total=0; nuclear@1: double time_total=0.; nuclear@1: nuclear@1: if(vf->ready_stateseekable)return(OV_ENOSEEK); nuclear@1: if(seconds<0)return(OV_EINVAL); nuclear@1: nuclear@1: /* which bitstream section does this time offset occur in? */ nuclear@1: for(link=0;linklinks;link++){ nuclear@1: double addsec = ov_time_total(vf,link); nuclear@1: if(secondspcmlengths[link*2+1]; nuclear@1: } nuclear@1: nuclear@1: if(link==vf->links)return(OV_EINVAL); nuclear@1: nuclear@1: /* enough information to convert time offset to pcm offset */ nuclear@1: { nuclear@1: ogg_int64_t target=pcm_total+(seconds-time_total)*vf->vi[link].rate; nuclear@1: return(ov_pcm_seek(vf,target)); nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: /* page-granularity version of ov_time_seek nuclear@1: returns zero on success, nonzero on failure */ nuclear@1: int ov_time_seek_page(OggVorbis_File *vf,double seconds){ nuclear@1: /* translate time to PCM position and call ov_pcm_seek */ nuclear@1: nuclear@1: int link=-1; nuclear@1: ogg_int64_t pcm_total=0; nuclear@1: double time_total=0.; nuclear@1: nuclear@1: if(vf->ready_stateseekable)return(OV_ENOSEEK); nuclear@1: if(seconds<0)return(OV_EINVAL); nuclear@1: nuclear@1: /* which bitstream section does this time offset occur in? */ nuclear@1: for(link=0;linklinks;link++){ nuclear@1: double addsec = ov_time_total(vf,link); nuclear@1: if(secondspcmlengths[link*2+1]; nuclear@1: } nuclear@1: nuclear@1: if(link==vf->links)return(OV_EINVAL); nuclear@1: nuclear@1: /* enough information to convert time offset to pcm offset */ nuclear@1: { nuclear@1: ogg_int64_t target=pcm_total+(seconds-time_total)*vf->vi[link].rate; nuclear@1: return(ov_pcm_seek_page(vf,target)); nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: /* tell the current stream offset cursor. Note that seek followed by nuclear@1: tell will likely not give the set offset due to caching */ nuclear@1: ogg_int64_t ov_raw_tell(OggVorbis_File *vf){ nuclear@1: if(vf->ready_stateoffset); nuclear@1: } nuclear@1: nuclear@1: /* return PCM offset (sample) of next PCM sample to be read */ nuclear@1: ogg_int64_t ov_pcm_tell(OggVorbis_File *vf){ nuclear@1: if(vf->ready_statepcm_offset); nuclear@1: } nuclear@1: nuclear@1: /* return time offset (seconds) of next PCM sample to be read */ nuclear@1: double ov_time_tell(OggVorbis_File *vf){ nuclear@1: int link=0; nuclear@1: ogg_int64_t pcm_total=0; nuclear@1: double time_total=0.f; nuclear@1: nuclear@1: if(vf->ready_stateseekable){ nuclear@1: pcm_total=ov_pcm_total(vf,-1); nuclear@1: time_total=ov_time_total(vf,-1); nuclear@1: nuclear@1: /* which bitstream section does this time offset occur in? */ nuclear@1: for(link=vf->links-1;link>=0;link--){ nuclear@1: pcm_total-=vf->pcmlengths[link*2+1]; nuclear@1: time_total-=ov_time_total(vf,link); nuclear@1: if(vf->pcm_offset>=pcm_total)break; nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: return((double)time_total+(double)(vf->pcm_offset-pcm_total)/vf->vi[link].rate); nuclear@1: } nuclear@1: nuclear@1: /* link: -1) return the vorbis_info struct for the bitstream section nuclear@1: currently being decoded nuclear@1: 0-n) to request information for a specific bitstream section nuclear@1: nuclear@1: In the case of a non-seekable bitstream, any call returns the nuclear@1: current bitstream. NULL in the case that the machine is not nuclear@1: initialized */ nuclear@1: nuclear@1: vorbis_info *ov_info(OggVorbis_File *vf,int link){ nuclear@1: if(vf->seekable){ nuclear@1: if(link<0) nuclear@1: if(vf->ready_state>=STREAMSET) nuclear@1: return vf->vi+vf->current_link; nuclear@1: else nuclear@1: return vf->vi; nuclear@1: else nuclear@1: if(link>=vf->links) nuclear@1: return NULL; nuclear@1: else nuclear@1: return vf->vi+link; nuclear@1: }else{ nuclear@1: return vf->vi; nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: /* grr, strong typing, grr, no templates/inheritence, grr */ nuclear@1: vorbis_comment *ov_comment(OggVorbis_File *vf,int link){ nuclear@1: if(vf->seekable){ nuclear@1: if(link<0) nuclear@1: if(vf->ready_state>=STREAMSET) nuclear@1: return vf->vc+vf->current_link; nuclear@1: else nuclear@1: return vf->vc; nuclear@1: else nuclear@1: if(link>=vf->links) nuclear@1: return NULL; nuclear@1: else nuclear@1: return vf->vc+link; nuclear@1: }else{ nuclear@1: return vf->vc; nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: static int host_is_big_endian() { nuclear@1: ogg_int32_t pattern = 0xfeedface; /* deadbeef */ nuclear@1: unsigned char *bytewise = (unsigned char *)&pattern; nuclear@1: if (bytewise[0] == 0xfe) return 1; nuclear@1: return 0; nuclear@1: } nuclear@1: nuclear@1: /* up to this point, everything could more or less hide the multiple nuclear@1: logical bitstream nature of chaining from the toplevel application nuclear@1: if the toplevel application didn't particularly care. However, at nuclear@1: the point that we actually read audio back, the multiple-section nuclear@1: nature must surface: Multiple bitstream sections do not necessarily nuclear@1: have to have the same number of channels or sampling rate. nuclear@1: nuclear@1: ov_read returns the sequential logical bitstream number currently nuclear@1: being decoded along with the PCM data in order that the toplevel nuclear@1: application can take action on channel/sample rate changes. This nuclear@1: number will be incremented even for streamed (non-seekable) streams nuclear@1: (for seekable streams, it represents the actual logical bitstream nuclear@1: index within the physical bitstream. Note that the accessor nuclear@1: functions above are aware of this dichotomy). nuclear@1: nuclear@1: ov_read_filter is exactly the same as ov_read except that it processes nuclear@1: the decoded audio data through a filter before packing it into the nuclear@1: requested format. This gives greater accuracy than applying a filter nuclear@1: after the audio has been converted into integral PCM. nuclear@1: nuclear@1: input values: buffer) a buffer to hold packed PCM data for return nuclear@1: length) the byte length requested to be placed into buffer nuclear@1: bigendianp) should the data be packed LSB first (0) or nuclear@1: MSB first (1) nuclear@1: word) word size for output. currently 1 (byte) or nuclear@1: 2 (16 bit short) nuclear@1: nuclear@1: return values: <0) error/hole in data (OV_HOLE), partial open (OV_EINVAL) nuclear@1: 0) EOF nuclear@1: n) number of bytes of PCM actually returned. The nuclear@1: below works on a packet-by-packet basis, so the nuclear@1: return length is not related to the 'length' passed nuclear@1: in, just guaranteed to fit. nuclear@1: nuclear@1: *section) set to the logical bitstream number */ nuclear@1: nuclear@1: long ov_read_filter(OggVorbis_File *vf,char *buffer,int length, nuclear@1: int bigendianp,int word,int sgned,int *bitstream, nuclear@1: void (*filter)(float **pcm,long channels,long samples,void *filter_param),void *filter_param){ nuclear@1: int i,j; nuclear@1: int host_endian = host_is_big_endian(); nuclear@1: int hs; nuclear@1: nuclear@1: float **pcm; nuclear@1: long samples; nuclear@1: nuclear@1: if(vf->ready_stateready_state==INITSET){ nuclear@1: samples=vorbis_synthesis_pcmout(&vf->vd,&pcm); nuclear@1: if(samples)break; nuclear@1: } nuclear@1: nuclear@1: /* suck in another packet */ nuclear@1: { nuclear@1: int ret=_fetch_and_process_packet(vf,NULL,1,1); nuclear@1: if(ret==OV_EOF) nuclear@1: return(0); nuclear@1: if(ret<=0) nuclear@1: return(ret); nuclear@1: } nuclear@1: nuclear@1: } nuclear@1: nuclear@1: if(samples>0){ nuclear@1: nuclear@1: /* yay! proceed to pack data into the byte buffer */ nuclear@1: nuclear@1: long channels=ov_info(vf,-1)->channels; nuclear@1: long bytespersample=word * channels; nuclear@1: vorbis_fpu_control fpu; nuclear@1: if(samples>length/bytespersample)samples=length/bytespersample; nuclear@1: nuclear@1: if(samples <= 0) nuclear@1: return OV_EINVAL; nuclear@1: nuclear@1: /* Here. */ nuclear@1: if(filter) nuclear@1: filter(pcm,channels,samples,filter_param); nuclear@1: nuclear@1: /* a tight loop to pack each size */ nuclear@1: { nuclear@1: int val; nuclear@1: if(word==1){ nuclear@1: int off=(sgned?0:128); nuclear@1: vorbis_fpu_setround(&fpu); nuclear@1: for(j=0;j127)val=127; nuclear@1: else if(val<-128)val=-128; nuclear@1: *buffer++=val+off; nuclear@1: } nuclear@1: vorbis_fpu_restore(fpu); nuclear@1: }else{ nuclear@1: int off=(sgned?0:32768); nuclear@1: nuclear@1: if(host_endian==bigendianp){ nuclear@1: if(sgned){ nuclear@1: nuclear@1: vorbis_fpu_setround(&fpu); nuclear@1: for(i=0;i32767)val=32767; nuclear@1: else if(val<-32768)val=-32768; nuclear@1: *dest=val; nuclear@1: dest+=channels; nuclear@1: } nuclear@1: } nuclear@1: vorbis_fpu_restore(fpu); nuclear@1: nuclear@1: }else{ nuclear@1: nuclear@1: vorbis_fpu_setround(&fpu); nuclear@1: for(i=0;i32767)val=32767; nuclear@1: else if(val<-32768)val=-32768; nuclear@1: *dest=val+off; nuclear@1: dest+=channels; nuclear@1: } nuclear@1: } nuclear@1: vorbis_fpu_restore(fpu); nuclear@1: nuclear@1: } nuclear@1: }else if(bigendianp){ nuclear@1: nuclear@1: vorbis_fpu_setround(&fpu); nuclear@1: for(j=0;j32767)val=32767; nuclear@1: else if(val<-32768)val=-32768; nuclear@1: val+=off; nuclear@1: *buffer++=(val>>8); nuclear@1: *buffer++=(val&0xff); nuclear@1: } nuclear@1: vorbis_fpu_restore(fpu); nuclear@1: nuclear@1: }else{ nuclear@1: int val; nuclear@1: vorbis_fpu_setround(&fpu); nuclear@1: for(j=0;j32767)val=32767; nuclear@1: else if(val<-32768)val=-32768; nuclear@1: val+=off; nuclear@1: *buffer++=(val&0xff); nuclear@1: *buffer++=(val>>8); nuclear@1: } nuclear@1: vorbis_fpu_restore(fpu); nuclear@1: nuclear@1: } nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: vorbis_synthesis_read(&vf->vd,samples); nuclear@1: hs=vorbis_synthesis_halfrate_p(vf->vi); nuclear@1: vf->pcm_offset+=(samples<current_link; nuclear@1: return(samples*bytespersample); nuclear@1: }else{ nuclear@1: return(samples); nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: long ov_read(OggVorbis_File *vf,char *buffer,int length, nuclear@1: int bigendianp,int word,int sgned,int *bitstream){ nuclear@1: return ov_read_filter(vf, buffer, length, bigendianp, word, sgned, bitstream, NULL, NULL); nuclear@1: } nuclear@1: nuclear@1: /* input values: pcm_channels) a float vector per channel of output nuclear@1: length) the sample length being read by the app nuclear@1: nuclear@1: return values: <0) error/hole in data (OV_HOLE), partial open (OV_EINVAL) nuclear@1: 0) EOF nuclear@1: n) number of samples of PCM actually returned. The nuclear@1: below works on a packet-by-packet basis, so the nuclear@1: return length is not related to the 'length' passed nuclear@1: in, just guaranteed to fit. nuclear@1: nuclear@1: *section) set to the logical bitstream number */ nuclear@1: nuclear@1: nuclear@1: nuclear@1: long ov_read_float(OggVorbis_File *vf,float ***pcm_channels,int length, nuclear@1: int *bitstream){ nuclear@1: nuclear@1: if(vf->ready_stateready_state==INITSET){ nuclear@1: float **pcm; nuclear@1: long samples=vorbis_synthesis_pcmout(&vf->vd,&pcm); nuclear@1: if(samples){ nuclear@1: int hs=vorbis_synthesis_halfrate_p(vf->vi); nuclear@1: if(pcm_channels)*pcm_channels=pcm; nuclear@1: if(samples>length)samples=length; nuclear@1: vorbis_synthesis_read(&vf->vd,samples); nuclear@1: vf->pcm_offset+=samples<current_link; nuclear@1: return samples; nuclear@1: nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: /* suck in another packet */ nuclear@1: { nuclear@1: int ret=_fetch_and_process_packet(vf,NULL,1,1); nuclear@1: if(ret==OV_EOF)return(0); nuclear@1: if(ret<=0)return(ret); nuclear@1: } nuclear@1: nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: extern float *vorbis_window(vorbis_dsp_state *v,int W); nuclear@1: nuclear@1: static void _ov_splice(float **pcm,float **lappcm, nuclear@1: int n1, int n2, nuclear@1: int ch1, int ch2, nuclear@1: float *w1, float *w2){ nuclear@1: int i,j; nuclear@1: float *w=w1; nuclear@1: int n=n1; nuclear@1: nuclear@1: if(n1>n2){ nuclear@1: n=n2; nuclear@1: w=w2; nuclear@1: } nuclear@1: nuclear@1: /* splice */ nuclear@1: for(j=0;jready_state==INITSET)break; nuclear@1: /* suck in another packet */ nuclear@1: { nuclear@1: int ret=_fetch_and_process_packet(vf,NULL,1,0); nuclear@1: if(ret<0 && ret!=OV_HOLE)return(ret); nuclear@1: } nuclear@1: } nuclear@1: return 0; nuclear@1: } nuclear@1: nuclear@1: /* make sure vf is INITSET and that we have a primed buffer; if nuclear@1: we're crosslapping at a stream section boundary, this also makes nuclear@1: sure we're sanity checking against the right stream information */ nuclear@1: static int _ov_initprime(OggVorbis_File *vf){ nuclear@1: vorbis_dsp_state *vd=&vf->vd; nuclear@1: while(1){ nuclear@1: if(vf->ready_state==INITSET) nuclear@1: if(vorbis_synthesis_pcmout(vd,NULL))break; nuclear@1: nuclear@1: /* suck in another packet */ nuclear@1: { nuclear@1: int ret=_fetch_and_process_packet(vf,NULL,1,0); nuclear@1: if(ret<0 && ret!=OV_HOLE)return(ret); nuclear@1: } nuclear@1: } nuclear@1: return 0; nuclear@1: } nuclear@1: nuclear@1: /* grab enough data for lapping from vf; this may be in the form of nuclear@1: unreturned, already-decoded pcm, remaining PCM we will need to nuclear@1: decode, or synthetic postextrapolation from last packets. */ nuclear@1: static void _ov_getlap(OggVorbis_File *vf,vorbis_info *vi,vorbis_dsp_state *vd, nuclear@1: float **lappcm,int lapsize){ nuclear@1: int lapcount=0,i; nuclear@1: float **pcm; nuclear@1: nuclear@1: /* try first to decode the lapping data */ nuclear@1: while(lapcountlapsize-lapcount)samples=lapsize-lapcount; nuclear@1: for(i=0;ichannels;i++) nuclear@1: memcpy(lappcm[i]+lapcount,pcm[i],sizeof(**pcm)*samples); nuclear@1: lapcount+=samples; nuclear@1: vorbis_synthesis_read(vd,samples); nuclear@1: }else{ nuclear@1: /* suck in another packet */ nuclear@1: int ret=_fetch_and_process_packet(vf,NULL,1,0); /* do *not* span */ nuclear@1: if(ret==OV_EOF)break; nuclear@1: } nuclear@1: } nuclear@1: if(lapcountvd,&pcm); nuclear@1: if(samples==0){ nuclear@1: for(i=0;ichannels;i++) nuclear@1: memset(lappcm[i]+lapcount,0,sizeof(**pcm)*lapsize-lapcount); nuclear@1: lapcount=lapsize; nuclear@1: }else{ nuclear@1: if(samples>lapsize-lapcount)samples=lapsize-lapcount; nuclear@1: for(i=0;ichannels;i++) nuclear@1: memcpy(lappcm[i]+lapcount,pcm[i],sizeof(**pcm)*samples); nuclear@1: lapcount+=samples; nuclear@1: } nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: /* this sets up crosslapping of a sample by using trailing data from nuclear@1: sample 1 and lapping it into the windowing buffer of sample 2 */ nuclear@1: int ov_crosslap(OggVorbis_File *vf1, OggVorbis_File *vf2){ nuclear@1: vorbis_info *vi1,*vi2; nuclear@1: float **lappcm; nuclear@1: float **pcm; nuclear@1: float *w1,*w2; nuclear@1: int n1,n2,i,ret,hs1,hs2; nuclear@1: nuclear@1: if(vf1==vf2)return(0); /* degenerate case */ nuclear@1: if(vf1->ready_stateready_statechannels); nuclear@1: n1=vorbis_info_blocksize(vi1,0)>>(1+hs1); nuclear@1: n2=vorbis_info_blocksize(vi2,0)>>(1+hs2); nuclear@1: w1=vorbis_window(&vf1->vd,0); nuclear@1: w2=vorbis_window(&vf2->vd,0); nuclear@1: nuclear@1: for(i=0;ichannels;i++) nuclear@1: lappcm[i]=alloca(sizeof(**lappcm)*n1); nuclear@1: nuclear@1: _ov_getlap(vf1,vi1,&vf1->vd,lappcm,n1); nuclear@1: nuclear@1: /* have a lapping buffer from vf1; now to splice it into the lapping nuclear@1: buffer of vf2 */ nuclear@1: /* consolidate and expose the buffer. */ nuclear@1: vorbis_synthesis_lapout(&vf2->vd,&pcm); nuclear@1: nuclear@1: #if 0 nuclear@1: _analysis_output_always("pcmL",0,pcm[0],n1*2,0,0,0); nuclear@1: _analysis_output_always("pcmR",0,pcm[1],n1*2,0,0,0); nuclear@1: #endif nuclear@1: nuclear@1: /* splice */ nuclear@1: _ov_splice(pcm,lappcm,n1,n2,vi1->channels,vi2->channels,w1,w2); nuclear@1: nuclear@1: /* done */ nuclear@1: return(0); nuclear@1: } nuclear@1: nuclear@1: static int _ov_64_seek_lap(OggVorbis_File *vf,ogg_int64_t pos, nuclear@1: int (*localseek)(OggVorbis_File *,ogg_int64_t)){ nuclear@1: vorbis_info *vi; nuclear@1: float **lappcm; nuclear@1: float **pcm; nuclear@1: float *w1,*w2; nuclear@1: int n1,n2,ch1,ch2,hs; nuclear@1: int i,ret; nuclear@1: nuclear@1: if(vf->ready_statechannels; nuclear@1: n1=vorbis_info_blocksize(vi,0)>>(1+hs); nuclear@1: w1=vorbis_window(&vf->vd,0); /* window arrays from libvorbis are nuclear@1: persistent; even if the decode state nuclear@1: from this link gets dumped, this nuclear@1: window array continues to exist */ nuclear@1: nuclear@1: lappcm=alloca(sizeof(*lappcm)*ch1); nuclear@1: for(i=0;ivd,lappcm,n1); nuclear@1: nuclear@1: /* have lapping data; seek and prime the buffer */ nuclear@1: ret=localseek(vf,pos); nuclear@1: if(ret)return ret; nuclear@1: ret=_ov_initprime(vf); nuclear@1: if(ret)return(ret); nuclear@1: nuclear@1: /* Guard against cross-link changes; they're perfectly legal */ nuclear@1: vi=ov_info(vf,-1); nuclear@1: ch2=vi->channels; nuclear@1: n2=vorbis_info_blocksize(vi,0)>>(1+hs); nuclear@1: w2=vorbis_window(&vf->vd,0); nuclear@1: nuclear@1: /* consolidate and expose the buffer. */ nuclear@1: vorbis_synthesis_lapout(&vf->vd,&pcm); nuclear@1: nuclear@1: /* splice */ nuclear@1: _ov_splice(pcm,lappcm,n1,n2,ch1,ch2,w1,w2); nuclear@1: nuclear@1: /* done */ nuclear@1: return(0); nuclear@1: } nuclear@1: nuclear@1: int ov_raw_seek_lap(OggVorbis_File *vf,ogg_int64_t pos){ nuclear@1: return _ov_64_seek_lap(vf,pos,ov_raw_seek); nuclear@1: } nuclear@1: nuclear@1: int ov_pcm_seek_lap(OggVorbis_File *vf,ogg_int64_t pos){ nuclear@1: return _ov_64_seek_lap(vf,pos,ov_pcm_seek); nuclear@1: } nuclear@1: nuclear@1: int ov_pcm_seek_page_lap(OggVorbis_File *vf,ogg_int64_t pos){ nuclear@1: return _ov_64_seek_lap(vf,pos,ov_pcm_seek_page); nuclear@1: } nuclear@1: nuclear@1: static int _ov_d_seek_lap(OggVorbis_File *vf,double pos, nuclear@1: int (*localseek)(OggVorbis_File *,double)){ nuclear@1: vorbis_info *vi; nuclear@1: float **lappcm; nuclear@1: float **pcm; nuclear@1: float *w1,*w2; nuclear@1: int n1,n2,ch1,ch2,hs; nuclear@1: int i,ret; nuclear@1: nuclear@1: if(vf->ready_statechannels; nuclear@1: n1=vorbis_info_blocksize(vi,0)>>(1+hs); nuclear@1: w1=vorbis_window(&vf->vd,0); /* window arrays from libvorbis are nuclear@1: persistent; even if the decode state nuclear@1: from this link gets dumped, this nuclear@1: window array continues to exist */ nuclear@1: nuclear@1: lappcm=alloca(sizeof(*lappcm)*ch1); nuclear@1: for(i=0;ivd,lappcm,n1); nuclear@1: nuclear@1: /* have lapping data; seek and prime the buffer */ nuclear@1: ret=localseek(vf,pos); nuclear@1: if(ret)return ret; nuclear@1: ret=_ov_initprime(vf); nuclear@1: if(ret)return(ret); nuclear@1: nuclear@1: /* Guard against cross-link changes; they're perfectly legal */ nuclear@1: vi=ov_info(vf,-1); nuclear@1: ch2=vi->channels; nuclear@1: n2=vorbis_info_blocksize(vi,0)>>(1+hs); nuclear@1: w2=vorbis_window(&vf->vd,0); nuclear@1: nuclear@1: /* consolidate and expose the buffer. */ nuclear@1: vorbis_synthesis_lapout(&vf->vd,&pcm); nuclear@1: nuclear@1: /* splice */ nuclear@1: _ov_splice(pcm,lappcm,n1,n2,ch1,ch2,w1,w2); nuclear@1: nuclear@1: /* done */ nuclear@1: return(0); nuclear@1: } nuclear@1: nuclear@1: int ov_time_seek_lap(OggVorbis_File *vf,double pos){ nuclear@1: return _ov_d_seek_lap(vf,pos,ov_time_seek); nuclear@1: } nuclear@1: nuclear@1: int ov_time_seek_page_lap(OggVorbis_File *vf,double pos){ nuclear@1: return _ov_d_seek_lap(vf,pos,ov_time_seek_page); nuclear@1: }