static ogg_int64_t _get_prev_page(OggVorbis_File *vf,ogg_page *og){ ogg_int64_t begin=vf->offset; ogg_int64_t end=begin; ogg_int64_t ret; ogg_int64_t offset=-1; while(offset==-1){ begin-=CHUNKSIZE; if(begin<0) begin=0; _seek_helper(vf,begin); 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{ offset=ret; } } } /* we have the offset. Actually snork and hold the page now */ _seek_helper(vf,offset); ret=_get_next_page(vf,og,CHUNKSIZE); if(ret<0) /* this shouldn't be possible */ return(OV_EFAULT); return(offset); }
/* finds each bitstream link one at a time using a bisection search (has to begin by knowing the offset of the lb's initial page). Recurses for each link so it can alloc the link storage after finding them all, then unroll and fill the cache at the same time */ static int _bisect_forward_serialno(OggVorbis_File *vf, ogg_int64_t begin, ogg_int64_t searched, ogg_int64_t end, ogg_uint32_t currentno, long m){ ogg_int64_t endsearched=end; ogg_int64_t next=end; ogg_page og={0,0,0,0}; 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; } _seek_helper(vf,bisect); ret=_get_next_page(vf,&og,-1); if(ret==OV_EREAD)return(OV_EREAD); if(ret<0 || ogg_page_serialno(&og)!=currentno){ endsearched=bisect; if(ret>=0)next=ret; }else{ searched=ret+og.header_len+og.body_len; } ogg_page_release(&og); } _seek_helper(vf,next); ret=_get_next_page(vf,&og,-1); if(ret==OV_EREAD)return(OV_EREAD); if(searched>=end || ret<0){ ogg_page_release(&og); vf->links=m+1; vf->offsets=_ogg_malloc((vf->links+1)*sizeof(*vf->offsets)); vf->serialnos=_ogg_malloc(vf->links*sizeof(*vf->serialnos)); vf->offsets[m+1]=searched; }else{ ret=_bisect_forward_serialno(vf,next,vf->offset, end,ogg_page_serialno(&og),m+1); ogg_page_release(&og); if(ret==OV_EREAD)return(OV_EREAD); } vf->offsets[m]=begin; vf->serialnos[m]=currentno; return(0); }
/* uses the local ogg_stream storage in vf; this is important for non-streaming input sources */ static int _fetch_headers(OggVorbis_File *vf,vorbis_info *vi,vorbis_comment *vc, long *serialno,ogg_page *og_ptr){ ogg_page og; ogg_packet op; int i,ret=0; if(!og_ptr){ ret=_get_next_page(vf,&og,CHUNKSIZE); if(ret==OV_EREAD)return(OV_EREAD); if(ret<0)return OV_ENOTVORBIS; og_ptr=&og; } if(serialno)*serialno=ogg_page_serialno(og_ptr); ogg_stream_init(&vf->os,ogg_page_serialno(og_ptr)); vf->ready_state=STREAMSET; /* extract the initial header from the first page and verify that the Ogg bitstream is in fact Vorbis data */ vorbis_info_init(vi); vorbis_comment_init(vc); i=0; while(i<3){ ogg_stream_pagein(&vf->os,og_ptr); while(i<3){ int result=ogg_stream_packetout(&vf->os,&op); if(result==0)break; if(result==-1){ ret=OV_EBADHEADER; goto bail_header; } if((ret=vorbis_synthesis_headerin(vi,vc,&op))){ goto bail_header; } i++; } if(i<3) if(_get_next_page(vf,og_ptr,CHUNKSIZE)<0){ ret=OV_EBADHEADER; goto bail_header; } } return 0; bail_header: vorbis_info_clear(vi); vorbis_comment_clear(vc); ogg_stream_clear(&vf->os); vf->ready_state=OPENED; return ret; }
static int32 _get_real_page_index (int32 i_page_link_list_head, int32 i_page_offset) { while (i_page_offset-- > 0 && i_page_link_list_head >= 0) { i_page_link_list_head = _get_next_page (i_page_link_list_head); } return i_page_link_list_head; }
int32 _overwrite_file_offset (directory *p_file_dir, int32 i_page_start, int32 i_page_offset, const void *p_addr, int32 len) { int32 i_rt_code, _l; len = _l = MIN (p_file_dir->parent_filesize, len); while (len > 0) { if ((i_rt_code = _write_page_offset (i_page_start, i_page_offset, p_addr, MIN (len, PAGE_SIZE - i_page_offset))) < 0) return i_rt_code; p_addr += MIN (len, PAGE_SIZE - i_page_offset); len -= MIN (len, PAGE_SIZE - i_page_offset); i_page_offset = 0; i_page_start = _get_next_page (i_page_start); } return _l; }
/* 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={0,0,0,0}; int i; ogg_int64_t ret; vf->vi=_ogg_realloc(vf->vi,vf->links*sizeof(*vf->vi)); vf->vc=_ogg_realloc(vf->vc,vf->links*sizeof(*vf->vc)); vf->dataoffsets=_ogg_malloc(vf->links*sizeof(*vf->dataoffsets)); vf->pcmlengths=_ogg_malloc(vf->links*2*sizeof(*vf->pcmlengths)); for(i=0;i<vf->links;i++){ if(i==0){ /* we already grabbed the initial header earlier. Just set the offset */ vf->dataoffsets[i]=dataoffset; _seek_helper(vf,dataoffset); }else{ /* seek to the location of the initial header */ _seek_helper(vf,vf->offsets[i]); if(_fetch_headers(vf,vf->vi+i,vf->vc+i,NULL,NULL)<0){ vf->dataoffsets[i]=-1; }else{ vf->dataoffsets[i]=vf->offset; } } /* fetch beginning PCM offset */ if(vf->dataoffsets[i]!=-1){ ogg_int64_t accumulated=0,pos; long lastblock=-1; int result; ogg_stream_reset_serialno(vf->os,vf->serialnos[i]); while(1){ ogg_packet op={0,0,0,0,0,0}; 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_serialno(&og)!=vf->serialnos[i]) break; pos=ogg_page_granulepos(&og); /* 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; } } ogg_packet_release(&op); if(pos!=-1){ /* pcm offset of last packet on the first audio page */ accumulated= pos-accumulated; break; } } /* 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; }
int ov_raw_seek(OggVorbis_File *vf,long pos){ ogg_stream_state work_os; if(vf->ready_state<OPENED)return(OV_EINVAL); if(!vf->seekable) return(OV_ENOSEEK); /* don't dump machine if we can't seek */ if(pos<0 || pos>vf->offsets[vf->links])return(OV_EINVAL); /* clear out decoding machine state */ vf->pcm_offset=-1; _decode_clear(vf); _seek_helper(vf,pos); /* we need to make sure the pcm_offset is set, but we don't want to advance the raw cursor past good packets just to get to the first with a granulepos. That's not equivalent behavior to beginning decoding as immediately after the seek position as possible. So, a hack. We use two stream states; a local scratch state and a the shared vf->os stream state. We use the local state to scan, and the shared state as a buffer for later decode. Unfortuantely, on the last page we still advance to last packet because the granulepos on the last page is not necessarily on a packet boundary, and we need to make sure the granpos is correct. */ { ogg_page og; ogg_packet op; int lastblock=0; int accblock=0; int thisblock=-1; int eosflag=0; memset(&work_os,0,sizeof(work_os));/* so that it's safe to clear it later even if we don't init it */ while(1){ if(vf->ready_state==STREAMSET){ /* snarf/scan a packet if we can */ int result=ogg_stream_packetout(&work_os,&op); if(result>0){ if(vf->vi[vf->current_link].codec_setup) thisblock=vorbis_packet_blocksize(vf->vi+vf->current_link,&op); if(eosflag) ogg_stream_packetout(&vf->os,NULL); else if(lastblock)accblock+=(lastblock+thisblock)>>2; if(op.granulepos!=-1){ int i,link=vf->current_link; ogg_int64_t granulepos=op.granulepos; for(i=0;i<link;i++) granulepos+=vf->pcmlengths[i]; vf->pcm_offset=granulepos-accblock; break; } lastblock=thisblock; continue; } } if(!lastblock){ if(_get_next_page(vf,&og,-1)<0){ vf->pcm_offset=ov_pcm_total(vf,-1); break; } }else{ /* huh? Bogus stream with packets but no granulepos */ vf->pcm_offset=-1; break; } /* has our decoding just traversed a bitstream boundary? */ if(vf->ready_state==STREAMSET) if(vf->current_serialno!=ogg_page_serialno(&og)){ _decode_clear(vf); /* clear out stream state */ ogg_stream_clear(&work_os); } if(vf->ready_state<STREAMSET){ int link; vf->current_serialno=ogg_page_serialno(&og); for(link=0;link<vf->links;link++) if(vf->serialnos[link]==vf->current_serialno)break; if(link==vf->links)goto seek_error; /* sign of a bogus stream. error out, leave machine uninitialized */ vf->current_link=link; ogg_stream_init(&vf->os,vf->current_serialno); ogg_stream_reset(&vf->os); ogg_stream_init(&work_os,vf->current_serialno); ogg_stream_reset(&work_os); vf->ready_state=STREAMSET; } ogg_stream_pagein(&vf->os,&og); ogg_stream_pagein(&work_os,&og); eosflag=ogg_page_eos(&og); } }
static int _process_packet(OggVorbis_File *vf,int readp){ ogg_page og; /* handle one packet. Try to fetch it from current stream state */ /* extract packets from page */ while(1){ /* process a packet if we can. If the machine isn't loaded, neither is a page */ if(vf->ready_state==INITSET){ while(1) { ogg_packet op; int result=ogg_stream_packetout(&vf->os,&op); ogg_int64_t granulepos; if(result==-1)return(OV_HOLE); /* hole in the data. */ if(result>0){ /* got a packet. process it */ granulepos=op.granulepos; if(!vorbis_synthesis(&vf->vb,&op)){ /* lazy check for lazy header handling. The header packets aren't audio, so if/when we submit them, vorbis_synthesis will reject them */ /* suck in the synthesis data and track bitrate */ { int oldsamples=vorbis_synthesis_pcmout(&vf->vd,NULL); vorbis_synthesis_blockin(&vf->vd,&vf->vb); vf->samptrack+=vorbis_synthesis_pcmout(&vf->vd,NULL)-oldsamples; vf->bittrack+=op.bytes*8; } /* update the pcm offset. */ if(granulepos!=-1 && !op.e_o_s){ int link=(vf->seekable?vf->current_link:0); int i,samples; /* this packet has a pcm_offset on it (the last packet completed on a page carries the offset) After processing (above), we know the pcm position of the *last* sample ready to be returned. Find the offset of the *first* As an aside, this trick is inaccurate if we begin reading anew right at the last page; the end-of-stream granulepos declares the last frame in the stream, and the last packet of the last page may be a partial frame. So, we need a previous granulepos from an in-sequence page to have a reference point. Thus the !op.e_o_s clause above */ samples=vorbis_synthesis_pcmout(&vf->vd,NULL); granulepos-=samples; for(i=0;i<link;i++) granulepos+=vf->pcmlengths[i]; vf->pcm_offset=granulepos; } return(1); } } else break; } } if(vf->ready_state>=OPENED){ if(!readp)return(0); if(_get_next_page(vf,&og,-1)<0)return(OV_EOF); /* eof. leave unitialized */ /* bitrate tracking; add the header's bytes here, the body bytes are done by packet above */ vf->bittrack+=og.header_len*8; /* has our decoding just traversed a bitstream boundary? */ if(vf->ready_state==INITSET){ if(vf->current_serialno!=ogg_page_serialno(&og)){ _decode_clear(vf); if(!vf->seekable){ vorbis_info_clear(vf->vi); vorbis_comment_clear(vf->vc); } } } } /* Do we need to load a new machine before submitting the page? */ /* This is different in the seekable and non-seekable cases. In the seekable case, we already have all the header information loaded and cached; we just initialize the machine with it and continue on our merry way. In the non-seekable (streaming) case, we'll only be at a boundary if we just left the previous logical bitstream and we're now nominally at the header of the next bitstream */ if(vf->ready_state!=INITSET){ int link; if(vf->ready_state<STREAMSET){ if(vf->seekable){ vf->current_serialno=ogg_page_serialno(&og); /* match the serialno to bitstream section. We use this rather than offset positions to avoid problems near logical bitstream boundaries */ for(link=0;link<vf->links;link++) if(vf->serialnos[link]==vf->current_serialno)break; if(link==vf->links)return(OV_EBADLINK); /* sign of a bogus stream. error out, leave machine uninitialized */ vf->current_link=link; ogg_stream_init(&vf->os,vf->current_serialno); ogg_stream_reset(&vf->os); vf->ready_state=STREAMSET; }else{ /* we're streaming */ /* fetch the three header packets, build the info struct */ int ret=_fetch_headers(vf,vf->vi,vf->vc,&vf->current_serialno,&og); if(ret)return(ret); vf->current_link++; link=0; } } _make_decode_ready(vf); } ogg_stream_pagein(&vf->os,&og); } }
static int _fetch_headers(OggVorbis_File *vf, vorbis_info *vi, vorbis_comment *vc, ogg_uint32_t *serialno, ogg_page *og_ptr){ ogg_page og={0,0,0,0}; ogg_packet op={0,0,0,0,0,0}; int i,ret; if(vf->ready_state>OPENED)_decode_clear(vf); if(!og_ptr){ ogg_int64_t llret=_get_next_page(vf,&og,CHUNKSIZE); if(llret==OV_EREAD)return OV_EREAD; if(llret<0)return OV_ENOTVORBIS; og_ptr=&og; } ogg_stream_reset_serialno(vf->os,ogg_page_serialno(og_ptr)); if(serialno)*serialno=vf->os->serialno; /* extract the initial header from the first page and verify that the Ogg bitstream is in fact Vorbis data */ vorbis_info_init(vi); vorbis_comment_init(vc); i=0; while(i<3){ ogg_stream_pagein(vf->os,og_ptr); while(i<3){ int result=ogg_stream_packetout(vf->os,&op); if(result==0)break; if(result==-1){ ret=OV_EBADHEADER; goto bail_header; } if((ret=vorbis_dsp_headerin(vi,vc,&op))){ goto bail_header; } i++; } if(i<3) if(_get_next_page(vf,og_ptr,CHUNKSIZE)<0){ ret=OV_EBADHEADER; goto bail_header; } } ogg_packet_release(&op); ogg_page_release(&og); vf->ready_state=LINKSET; return 0; bail_header: ogg_packet_release(&op); ogg_page_release(&og); vorbis_info_clear(vi); vorbis_comment_clear(vc); vf->ready_state=OPENED; return ret; }