📄 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-2002 * * BY THE Xiph.Org FOUNDATION http://www.xiph.org/ * * * ******************************************************************** function: stdio-based convenience library for opening/seeking/decoding ********************************************************************/#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_buffer(&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,long 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 */static long _get_next_page(OggVorbis_File *vf,ogg_page *og,int 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 */ long 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 */static long _get_prev_page(OggVorbis_File *vf,ogg_page *og){ long begin=vf->offset; long ret; int offset=-1; while(offset==-1){ begin-=CHUNKSIZE; if(begin<0) begin=0; _seek_helper(vf,begin); while(vf->offset<begin+CHUNKSIZE){ ret=_get_next_page(vf,og,begin+CHUNKSIZE-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, long begin, long searched, long end, long currentno, long m){ long endsearched=end; long next=end; ogg_page og; long ret; /* the below guards against garbage seperating the last and first pages of two links. */ while(searched<endsearched){ long 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; } } _seek_helper(vf,next); ret=_get_next_page(vf,&og,-1); if(ret==OV_EREAD)return(OV_EREAD); if(searched>=end || ret<0){ vf->links=m+1; vf->offsets=(ogg_int64_t *)_ogg_malloc((m+2)*sizeof(*vf->offsets)); vf->offsets[m+1]=searched; }else{ ret=_bisect_forward_serialno(vf,next,vf->offset, end,ogg_page_serialno(&og),m+1); if(ret==OV_EREAD)return(OV_EREAD); } vf->offsets[m]=begin; 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;}/* 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, long dataoffset){ ogg_page og; int i,ret; vf->vi=(vorbis_info *)_ogg_realloc(vf->vi,vf->links*sizeof(*vf->vi)); vf->vc=(vorbis_comment *)_ogg_realloc(vf->vc,vf->links*sizeof(*vf->vc)); vf->dataoffsets=(ogg_int64_t *)_ogg_malloc(vf->links*sizeof(*vf->dataoffsets)); vf->pcmlengths=(ogg_int64_t *)_ogg_malloc(vf->links*sizeof(*vf->pcmlengths)); vf->serialnos=(long *)_ogg_malloc(vf->links*sizeof(*vf->serialnos)); 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; }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; ogg_stream_clear(&vf->os); } } /* get the serial number and PCM length of this link. To do this, get the last page of the stream */ { long end=vf->offsets[i+1]; _seek_helper(vf,end); while(1){ ret=_get_prev_page(vf,&og); if(ret<0){ /* this should not be possible */ vorbis_info_clear(vf->vi+i); vorbis_comment_clear(vf->vc+i); break; } if(ogg_page_granulepos(&og)!=-1){ vf->serialnos[i]=ogg_page_serialno(&og); vf->pcmlengths[i]=ogg_page_granulepos(&og); break; } vf->offset=ret; } } }}static void _make_decode_ready(OggVorbis_File *vf){ if(vf->ready_state!=STREAMSET)return; if(vf->seekable){ vorbis_synthesis_init(&vf->vd,vf->vi+vf->current_link); }else{ vorbis_synthesis_init(&vf->vd,vf->vi); } vorbis_block_init(&vf->vd,&vf->vb); vf->ready_state=INITSET; return;}static int _open_seekable2(OggVorbis_File *vf){ long serialno=vf->current_serialno,end; long dataoffset=vf->offset; ogg_page og; /* we're partially open and have a first link header state in storage in vf */ /* we can seek, so set out learning all about this file */ (vf->callbacks.seek_func)(vf->datasource,0,SEEK_END); vf->offset=vf->end=(vf->callbacks.tell_func)(vf->datasource); /* We get the offset for the last page of the physical bitstream. Most OggVorbis files will contain a single logical bitstream */ end=_get_prev_page(vf,&og); if(end<0){ ov_clear(vf); return(end); } /* more than one logical bitstream? */ if(ogg_page_serialno(&og)!=serialno){ /* Chained bitstream. Bisect-search each logical bitstream section. Do so based on serial number only */ if(_bisect_forward_serialno(vf,0,0,end+1,serialno,0)<0){ ov_clear(vf); return(OV_EREAD); } }else{ /* Only one logical bitstream */ if(_bisect_forward_serialno(vf,0,end,end+1,serialno,0)){ ov_clear(vf); return(OV_EREAD); } } /* the initial header memory is referenced by vf after; don't free it */ _prefetch_all_headers(vf,dataoffset); return(ov_raw_seek(vf,0));}/* clear out the current logical bitstream decoder */ static void _decode_clear(OggVorbis_File *vf){ ogg_stream_clear(&vf->os); vorbis_dsp_clear(&vf->vd); vorbis_block_clear(&vf->vb); vf->ready_state=OPENED; vf->bittrack=0; vf->samptrack=0;}/* fetch and process a packet. Handles the case where we're at a bitstream boundary and dumps the decoding machine. If the decoding machine is unloaded, it loads it. It also keeps pcm_offset up to date (seek and read both use this. seek uses a special hack with readp). return: <0) error, OV_HOLE (lost packet) or OV_EOF 0) need more data (only if readp==0) 1) got a packet */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];
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -