📄 vorbisfile.c
字号:
/******************************************************************** * * * THIS FILE IS PART OF THE OggVorbis 'TREMOR' CODEC SOURCE CODE. * * * * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * * * * THE OggVorbis 'TREMOR' SOURCE CODE IS (C) COPYRIGHT 1994-2003 * * BY THE Xiph.Org FOUNDATION http://www.xiph.org/ * * * ******************************************************************** function: stdio-based convenience library for opening/seeking/decoding last mod: $Id: vorbisfile.c,v 1.6 2003/03/30 23:40:56 xiphmont Exp $ ********************************************************************/#include <stdlib.h>#include <stdio.h>#include <errno.h>#include <string.h>#include <math.h>#include "ivorbiscodec.h"#include "ivorbisfile.h"#include "os.h"#include "misc.h"/* A 'chained bitstream' is a Vorbis bitstream that contains more than one logical bitstream arranged end to end (the only form of Ogg multiplexing allowed in a Vorbis bitstream; grouping [parallel multiplexing] is not allowed in Vorbis) *//* A Vorbis file can be played beginning to end (streamed) without worrying ahead of time about chaining (see decoder_example.c). If we have the whole file, however, and want random access (seeking/scrubbing) or desire to know the total length/time of a file, we need to account for the possibility of chaining. *//* We can handle things a number of ways; we can determine the entire bitstream structure right off the bat, or find pieces on demand. This example determines and caches structure for the entire bitstream, but builds a virtual decoder on the fly when moving between links in the chain. *//* There are also different ways to implement seeking. Enough information exists in an Ogg bitstream to seek to sample-granularity positions in the output. Or, one can seek by picking some portion of the stream roughly in the desired area if we only want coarse navigation through the stream. *//************************************************************************* * Many, many internal helpers. The intention is not to be confusing; * rampant duplication and monolithic function implementation would be * harder to understand anyway. The high level functions are last. Begin * grokking near the end of the file *//* read a little more data from the file/pipe into the ogg_sync framer */static long _get_data(OggVorbis_File *vf){ errno=0; if(vf->datasource){ char *buffer=ogg_sync_bufferin(vf->oy,CHUNKSIZE); long bytes=(vf->callbacks.read_func)(buffer,1,CHUNKSIZE,vf->datasource); if(bytes>0)ogg_sync_wrote(vf->oy,bytes); if(bytes==0 && errno)return(-1); return(bytes); }else return(0);}/* save a tiny smidge of verbosity to make the code more readable */static void _seek_helper(OggVorbis_File *vf,ogg_int64_t offset){ if(vf->datasource){ (vf->callbacks.seek_func)(vf->datasource, offset, SEEK_SET); vf->offset=offset; ogg_sync_reset(vf->oy); }else{ /* shouldn't happen unless someone writes a broken callback */ return; }}/* The read/seek functions track absolute position within the stream *//* from the head of the stream, get the next page. boundary specifies if the function is allowed to fetch more data from the stream (and how much) or only use internally buffered data. boundary: -1) unbounded search 0) read no additional data; use cached only n) search for a new page beginning for n bytes return: <0) did not find a page (OV_FALSE, OV_EOF, OV_EREAD) n) found a page at absolute offset n produces a refcounted page */static ogg_int64_t _get_next_page(OggVorbis_File *vf,ogg_page *og, ogg_int64_t boundary){ if(boundary>0)boundary+=vf->offset; while(1){ long more; if(boundary>0 && vf->offset>=boundary)return(OV_FALSE); more=ogg_sync_pageseek(vf->oy,og); if(more<0){ /* skipped n bytes */ vf->offset-=more; }else{ if(more==0){ /* send more paramedics */ if(!boundary)return(OV_FALSE); { long ret=_get_data(vf); if(ret==0)return(OV_EOF); if(ret<0)return(OV_EREAD); } }else{ /* got a page. Return the offset at the page beginning, advance the internal offset past the page end */ ogg_int64_t ret=vf->offset; vf->offset+=more; return(ret); } } }}/* find the latest page beginning before the current stream cursor position. Much dirtier than the above as Ogg doesn't have any backward search linkage. no 'readp' as it will certainly have to read. *//* returns offset or OV_EREAD, OV_FAULT and produces a refcounted page */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 *//* consumes the page that's passed in (if any) */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(!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; 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; } } ogg_packet_release(&op); ogg_page_release(&og); 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;}/* last step of the OggVorbis_File initialization; get all the vorbis_info structs and PCM positions. Only called by the seekable initialization (local stream storage is hacked slightly; pay attention to how that's done) *//* 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; } /* get the PCM length of this link. To do this, get the last page of the stream */ { ogg_int64_t end=vf->offsets[i+1]; _seek_helper(vf,end); while(1){ ret=_get_prev_page(vf,&og); if(ret<0){ /* this should not be possible */ vorbis_info_clear(vf->vi+i); vorbis_comment_clear(vf->vc+i); break; } if(ogg_page_granulepos(&og)!=-1){ vf->pcmlengths[i*2+1]=ogg_page_granulepos(&og)-vf->pcmlengths[i*2];
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -