summaryrefslogtreecommitdiff
path: root/lib/vorbisfile.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vorbisfile.c')
-rw-r--r--lib/vorbisfile.c1156
1 files changed, 1156 insertions, 0 deletions
diff --git a/lib/vorbisfile.c b/lib/vorbisfile.c
new file mode 100644
index 00000000..eb49fc80
--- /dev/null
+++ b/lib/vorbisfile.c
@@ -0,0 +1,1156 @@
+/********************************************************************
+ * *
+ * THIS FILE IS PART OF THE Ogg Vorbis SOFTWARE CODEC SOURCE CODE. *
+ * USE, DISTRIBUTION AND REPRODUCTION OF THIS SOURCE IS GOVERNED BY *
+ * THE GNU PUBLIC LICENSE 2, WHICH IS INCLUDED WITH THIS SOURCE. *
+ * PLEASE READ THESE TERMS DISTRIBUTING. *
+ * *
+ * THE OggSQUISH SOURCE CODE IS (C) COPYRIGHT 1994-2000 *
+ * by Monty <monty@xiph.org> and The XIPHOPHORUS Company *
+ * http://www.xiph.org/ *
+ * *
+ ********************************************************************
+
+ function: stdio-based convenience library for opening/seeking/decoding
+ last mod: $Id: vorbisfile.c,v 1.25.4.1 2000/08/15 08:33:46 xiphmont Exp $
+
+ ********************************************************************/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+
+#include "vorbis/codec.h"
+#include "vorbis/vorbisfile.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 course 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 */
+#define CHUNKSIZE 4096
+static long _get_data(OggVorbis_File *vf){
+ char *buffer=ogg_sync_buffer(&vf->oy,CHUNKSIZE);
+ long bytes=(vf->callbacks.read_func)(buffer,1,CHUNKSIZE,vf->datasource);
+ ogg_sync_wrote(&vf->oy,bytes);
+ return(bytes);
+}
+
+/* save a tiny smidge of verbosity to make the code more readable */
+static void _seek_helper(OggVorbis_File *vf,long offset){
+ (vf->callbacks.seek_func)(vf->datasource, offset, SEEK_SET);
+ vf->offset=offset;
+ ogg_sync_reset(&vf->oy);
+}
+
+/* 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: -1) did not find a page
+ 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(-1);
+ 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(-1);
+ if(_get_data(vf)<=0)return(-1);
+ }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. */
+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;
+ _seek_helper(vf,begin);
+ while(vf->offset<begin+CHUNKSIZE){
+ ret=_get_next_page(vf,og,begin+CHUNKSIZE-vf->offset);
+ if(ret==-1){
+ 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==-1){
+ /* this shouldn't be possible */
+ fprintf(stderr,"Missed page fencepost at end of logical bitstream. "
+ "Exiting.\n");
+ exit(1);
+ }
+ 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 void _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<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(searched>=end || ret==-1){
+ vf->links=m+1;
+ vf->offsets=malloc((m+2)*sizeof(int64_t));
+ vf->offsets[m+1]=searched;
+ }else{
+ _bisect_forward_serialno(vf,next,vf->offset,
+ end,ogg_page_serialno(&og),m+1);
+ }
+
+ vf->offsets[m]=begin;
+}
+
+/* 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;
+ ogg_packet op;
+ int i,ret;
+
+ ret=_get_next_page(vf,&og,CHUNKSIZE);
+ if(ret==-1){
+ fprintf(stderr,"Did not find initial header for bitstream.\n");
+ return -1;
+ }
+
+ if(serialno)*serialno=ogg_page_serialno(&og);
+ ogg_stream_init(&vf->os,ogg_page_serialno(&og));
+
+ /* 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);
+ while(i<3){
+ int result=ogg_stream_packetout(&vf->os,&op);
+ if(result==0)break;
+ if(result==-1){
+ fprintf(stderr,"Corrupt header in logical bitstream.\n");
+ goto bail_header;
+ }
+ if(vorbis_synthesis_headerin(vi,vc,&op)){
+ fprintf(stderr,"Illegal header in logical bitstream.\n");
+ goto bail_header;
+ }
+ i++;
+ }
+ if(i<3)
+ if(_get_next_page(vf,&og,1)<0){
+ fprintf(stderr,"Missing header in logical bitstream.\n");
+ goto bail_header;
+ }
+ }
+ return 0;
+
+ bail_header:
+ vorbis_info_clear(vi);
+ vorbis_comment_clear(vc);
+ ogg_stream_clear(&vf->os);
+ return -1;
+}
+
+/* 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) */
+static void _prefetch_all_headers(OggVorbis_File *vf,vorbis_info *first_i,
+ vorbis_comment *first_c,
+ long dataoffset){
+ ogg_page og;
+ int i,ret;
+
+ vf->vi=calloc(vf->links,sizeof(vorbis_info));
+ vf->vc=calloc(vf->links,sizeof(vorbis_info));
+ vf->dataoffsets=malloc(vf->links*sizeof(int64_t));
+ vf->pcmlengths=malloc(vf->links*sizeof(int64_t));
+ vf->serialnos=malloc(vf->links*sizeof(long));
+
+ for(i=0;i<vf->links;i++){
+ if(first_i && first_c && i==0){
+ /* we already grabbed the initial header earlier. This just
+ saves the waste of grabbing it again */
+ memcpy(vf->vi+i,first_i,sizeof(vorbis_info));
+ memcpy(vf->vc+i,first_c,sizeof(vorbis_comment));
+ 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)==-1){
+ fprintf(stderr,"Error opening logical bitstream #%d.\n\n",i+1);
+ 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==-1){
+ /* this should not be possible */
+ fprintf(stderr,"Could not find last page of logical "
+ "bitstream #%d\n\n",i);
+ vorbis_info_clear(vf->vi+i);
+ vorbis_comment_clear(vf->vc+i);
+ break;
+ }
+ if(ogg_page_frameno(&og)!=-1){
+ vf->serialnos[i]=ogg_page_serialno(&og);
+ vf->pcmlengths[i]=ogg_page_frameno(&og);
+ break;
+ }
+ }
+ }
+ }
+}
+
+static int _make_decode_ready(OggVorbis_File *vf){
+ if(vf->decode_ready)exit(1);
+ vorbis_synthesis_init(&vf->vd,vf->vi);
+ vorbis_block_init(&vf->vd,&vf->vb);
+ vf->decode_ready=1;
+ return(0);
+}
+
+static int _open_seekable(OggVorbis_File *vf){
+ vorbis_info initial_i;
+ vorbis_comment initial_c;
+ long serialno,end;
+ int ret;
+ long dataoffset;
+ ogg_page og;
+
+ /* is this even vorbis...? */
+ ret=_fetch_headers(vf,&initial_i,&initial_c,&serialno);
+ dataoffset=vf->offset;
+ ogg_stream_clear(&vf->os);
+ if(ret==-1)return(-1);
+
+ /* we can seek, so set out learning all about this file */
+ vf->seekable=1;
+ (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);
+
+ /* moer 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 */
+ _bisect_forward_serialno(vf,0,0,end+1,serialno,0);
+
+ }else{
+
+ /* Only one logical bitstream */
+ _bisect_forward_serialno(vf,0,end,end+1,serialno,0);
+
+ }
+
+ _prefetch_all_headers(vf,&initial_i,&initial_c,dataoffset);
+ return(ov_raw_seek(vf,0));
+
+}
+
+static int _open_nonseekable(OggVorbis_File *vf){
+ /* we cannot seek. Set up a 'single' (current) logical bitstream entry */
+ vf->links=1;
+ vf->vi=calloc(vf->links,sizeof(vorbis_info));
+ vf->vc=calloc(vf->links,sizeof(vorbis_info));
+
+ /* Try to fetch the headers, maintaining all the storage */
+ if(_fetch_headers(vf,vf->vi,vf->vc,&vf->current_serialno)==-1)return(-1);
+ _make_decode_ready(vf);
+
+ return 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->decode_ready=0;
+
+ 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: -1) hole in the data (lost packet)
+ 0) need more date (only if readp==0)/eof
+ 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->decode_ready){
+ ogg_packet op;
+ int result=ogg_stream_packetout(&vf->os,&op);
+ int64_t frameno;
+
+ /* if(result==-1)return(-1); hole in the data. For now, swallow
+ and go. We'll need to add a real
+ error code in a bit. */
+ if(result>0){
+ /* got a packet. process it */
+ frameno=op.frameno;
+ 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(frameno!=-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
+ frameno 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 frameno 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);
+
+ frameno-=samples;
+ for(i=0;i<link;i++)
+ frameno+=vf->pcmlengths[i];
+ vf->pcm_offset=frameno;
+ }
+ return(1);
+ }
+ }
+ }
+
+ if(!readp)return(0);
+ if(_get_next_page(vf,&og,-1)<0)return(0); /* 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->decode_ready){
+ if(vf->current_serialno!=ogg_page_serialno(&og)){
+ _decode_clear(vf);
+ }
+ }
+
+ /* 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->decode_ready){
+ int link;
+ 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(-1); /* 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);
+
+ }else{
+ /* we're streaming */
+ /* fetch the three header packets, build the info struct */
+
+ _fetch_headers(vf,vf->vi,vf->vc,&vf->current_serialno);
+ vf->current_link++;
+ link=0;
+ }
+
+ _make_decode_ready(vf);
+ }
+ ogg_stream_pagein(&vf->os,&og);
+ }
+}
+
+/**********************************************************************
+ * The helpers are over; it's all toplevel interface from here on out */
+
+/* clear out the OggVorbis_File struct */
+int ov_clear(OggVorbis_File *vf){
+ if(vf){
+ vorbis_block_clear(&vf->vb);
+ vorbis_dsp_clear(&vf->vd);
+ ogg_stream_clear(&vf->os);
+
+ if(vf->vi && vf->links){
+ int i;
+ for(i=0;i<vf->links;i++){
+ vorbis_info_clear(vf->vi+i);
+ vorbis_comment_clear(vf->vc+i);
+ }
+ free(vf->vi);
+ free(vf->vc);
+ }
+ if(vf->dataoffsets)free(vf->dataoffsets);
+ if(vf->pcmlengths)free(vf->pcmlengths);
+ if(vf->serialnos)free(vf->serialnos);
+ if(vf->offsets)free(vf->offsets);
+ ogg_sync_clear(&vf->oy);
+ if(vf->datasource)(vf->callbacks.close_func)(vf->datasource);
+ memset(vf,0,sizeof(OggVorbis_File));
+ }
+#ifdef DEBUG_LEAKS
+ _VDBG_dump();
+#endif
+ return(0);
+}
+
+static int _fseek64_wrap(FILE *f,int64_t off,int whence){
+ return fseek(f,(int)off,whence);
+}
+
+/* inspects the OggVorbis file and finds/documents all the logical
+ bitstreams contained in it. Tries to be tolerant of logical
+ bitstream sections that are truncated/woogie.
+
+ return: -1) error
+ 0) OK
+*/
+
+int ov_open(FILE *f,OggVorbis_File *vf,char *initial,long ibytes){
+ ov_callbacks callbacks = {
+ (size_t (*)(void *, size_t, size_t, void *)) fread,
+ (int (*)(void *, int64_t, int)) _fseek64_wrap,
+ (int (*)(void *)) fclose,
+ (long (*)(void *)) ftell
+ };
+
+ return ov_open_callbacks((void *)f, vf, initial, ibytes, callbacks);
+}
+
+
+int ov_open_callbacks(void *f,OggVorbis_File *vf,char *initial,long ibytes,
+ ov_callbacks callbacks)
+{
+ long offset=callbacks.seek_func(f,0,SEEK_CUR);
+ int ret;
+
+ memset(vf,0,sizeof(OggVorbis_File));
+ vf->datasource=f;
+ vf->callbacks = callbacks;
+
+ /* init the framing state */
+ ogg_sync_init(&vf->oy);
+
+ /* perhaps some data was previously read into a buffer for testing
+ against other stream types. Allow initialization from this
+ previously read data (as we may be reading from a non-seekable
+ stream) */
+ if(initial){
+ char *buffer=ogg_sync_buffer(&vf->oy,ibytes);
+ memcpy(buffer,initial,ibytes);
+ ogg_sync_wrote(&vf->oy,ibytes);
+ }
+
+ /* can we seek? Stevens suggests the seek test was portable */
+ if(offset!=-1){
+ ret=_open_seekable(vf);
+ }else{
+ ret=_open_nonseekable(vf);
+ }
+ if(ret){
+ vf->datasource=NULL;
+ ov_clear(vf);
+ }
+ return(ret);
+}
+
+/* How many logical bitstreams in this physical bitstream? */
+long ov_streams(OggVorbis_File *vf){
+ return vf->links;
+}
+
+/* Is the FILE * associated with vf seekable? */
+long ov_seekable(OggVorbis_File *vf){
+ return vf->seekable;
+}
+
+/* returns the bitrate for a given logical bitstream or the entire
+ physical bitstream. If the file is open for random access, it will
+ find the *actual* average bitrate. If the file is streaming, it
+ returns the nominal bitrate (if set) else the average of the
+ upper/lower bounds (if set) else -1 (unset).
+
+ If you want the actual bitrate field settings, get them from the
+ vorbis_info structs */
+
+long ov_bitrate(OggVorbis_File *vf,int i){
+ if(i>=vf->links)return(-1);
+ if(!vf->seekable && i!=0)return(ov_bitrate(vf,0));
+ if(i<0){
+ int64_t bits=0;
+ int i;
+ for(i=0;i<vf->links;i++)
+ bits+=(vf->offsets[i+1]-vf->dataoffsets[i])*8;
+ return(rint(bits/ov_time_total(vf,-1)));
+ }else{
+ if(vf->seekable){
+ /* return the actual bitrate */
+ return(rint((vf->offsets[i+1]-vf->dataoffsets[i])*8/ov_time_total(vf,i)));
+ }else{
+ /* return nominal if set */
+ if(vf->vi[i].bitrate_nominal>0){
+ return vf->vi[i].bitrate_nominal;
+ }else{
+ if(vf->vi[i].bitrate_upper>0){
+ if(vf->vi[i].bitrate_lower>0){
+ return (vf->vi[i].bitrate_upper+vf->vi[i].bitrate_lower)/2;
+ }else{
+ return vf->vi[i].bitrate_upper;
+ }
+ }
+ return(-1);
+ }
+ }
+ }
+}
+
+/* returns the actual bitrate since last call. returns -1 if no
+ additional data to offer since last call (or at beginning of stream) */
+long ov_bitrate_instant(OggVorbis_File *vf){
+ int link=(vf->seekable?vf->current_link:0);
+ long ret;
+ if(vf->samptrack==0)return(-1);
+ ret=vf->bittrack/vf->samptrack*vf->vi[link].rate+.5;
+ vf->bittrack=0.;
+ vf->samptrack=0.;
+ return(ret);
+}
+
+/* Guess */
+long ov_serialnumber(OggVorbis_File *vf,int i){
+ if(i>=vf->links)return(-1);
+ if(!vf->seekable && i>=0)return(ov_serialnumber(vf,-1));
+ if(i<0){
+ return(vf->current_serialno);
+ }else{
+ return(vf->serialnos[i]);
+ }
+}
+
+/* returns: total raw (compressed) length of content if i==-1
+ raw (compressed) length of that logical bitstream for i==0 to n
+ -1 if the stream is not seekable (we can't know the length)
+*/
+int64_t ov_raw_total(OggVorbis_File *vf,int i){
+ if(!vf->seekable || i>=vf->links)return(-1);
+ if(i<0){
+ long acc=0;
+ int i;
+ for(i=0;i<vf->links;i++)
+ acc+=ov_raw_total(vf,i);
+ return(acc);
+ }else{
+ return(vf->offsets[i+1]-vf->offsets[i]);
+ }
+}
+
+/* returns: total PCM length (samples) of content if i==-1
+ PCM length (samples) of that logical bitstream for i==0 to n
+ -1 if the stream is not seekable (we can't know the length)
+*/
+int64_t ov_pcm_total(OggVorbis_File *vf,int i){
+ if(!vf->seekable || i>=vf->links)return(-1);
+ if(i<0){
+ int64_t acc=0;
+ int i;
+ for(i=0;i<vf->links;i++)
+ acc+=ov_pcm_total(vf,i);
+ return(acc);
+ }else{
+ return(vf->pcmlengths[i]);
+ }
+}
+
+/* returns: total seconds of content if i==-1
+ seconds in that logical bitstream for i==0 to n
+ -1 if the stream is not seekable (we can't know the length)
+*/
+double ov_time_total(OggVorbis_File *vf,int i){
+ if(!vf->seekable || i>=vf->links)return(-1);
+ if(i<0){
+ double acc=0;
+ int i;
+ for(i=0;i<vf->links;i++)
+ acc+=ov_time_total(vf,i);
+ return(acc);
+ }else{
+ return((float)(vf->pcmlengths[i])/vf->vi[i].rate);
+ }
+}
+
+/* seek to an offset relative to the *compressed* data. This also
+ immediately sucks in and decodes pages to update the PCM cursor. It
+ will cross a logical bitstream boundary, but only if it can't get
+ any packets out of the tail of the bitstream we seek to (so no
+ surprises).
+
+ returns zero on success, nonzero on failure */
+
+int ov_raw_seek(OggVorbis_File *vf,long pos){
+
+ if(!vf->seekable)return(-1); /* don't dump machine if we can't seek */
+ if(pos<0 || pos>vf->offsets[vf->links])goto seek_error;
+
+ /* clear out decoding machine state */
+ vf->pcm_offset=-1;
+ _decode_clear(vf);
+
+ /* seek */
+ _seek_helper(vf,pos);
+
+ /* we need to make sure the pcm_offset is set. We use the
+ _fetch_packet helper to process one packet with readp set, then
+ call it until it returns '0' with readp not set (the last packet
+ from a page has the 'frameno' field set, and that's how the
+ helper updates the offset */
+
+ switch(_process_packet(vf,1)){
+ case 0:
+ /* oh, eof. There are no packets remaining. Set the pcm offset to
+ the end of file */
+ vf->pcm_offset=ov_pcm_total(vf,-1);
+ return(0);
+ case -1:
+ /* error! missing data or invalid bitstream structure */
+ goto seek_error;
+ default:
+ /* all OK */
+ break;
+ }
+
+ while(1){
+ switch(_process_packet(vf,0)){
+ case 0:
+ /* the offset is set. If it's a bogus bitstream with no offset
+ information, it's not but that's not our fault. We still run
+ gracefully, we're just missing the offset */
+ return(0);
+ case -1:
+ /* error! missing data or invalid bitstream structure */
+ goto seek_error;
+ default:
+ /* continue processing packets */
+ break;
+ }
+ }
+
+ seek_error:
+ /* dump the machine so we're in a known state */
+ vf->pcm_offset=-1;
+ _decode_clear(vf);
+ return -1;
+}
+
+/* seek to a sample offset relative to the decompressed pcm stream
+
+ returns zero on success, nonzero on failure */
+
+int ov_pcm_seek(OggVorbis_File *vf,int64_t pos){
+ int link=-1;
+ int64_t total=ov_pcm_total(vf,-1);
+
+ if(!vf->seekable)return(-1); /* don't dump machine if we can't seek */
+ if(pos<0 || pos>total)goto seek_error;
+
+ /* which bitstream section does this pcm offset occur in? */
+ for(link=vf->links-1;link>=0;link--){
+ total-=vf->pcmlengths[link];
+ if(pos>=total)break;
+ }
+
+ /* search within the logical bitstream for the page with the highest
+ pcm_pos preceeding (or equal to) pos. There is a danger here;
+ missing pages or incorrect frame number information in the
+ bitstream could make our task impossible. Account for that (it
+ would be an error condition) */
+ {
+ int64_t target=pos-total;
+ long end=vf->offsets[link+1];
+ long begin=vf->offsets[link];
+ long best=begin;
+
+ ogg_page og;
+ while(begin<end){
+ long bisect;
+ long ret;
+
+ if(end-begin<CHUNKSIZE){
+ bisect=begin;
+ }else{
+ bisect=(end+begin)/2;
+ }
+
+ _seek_helper(vf,bisect);
+ ret=_get_next_page(vf,&og,end-bisect);
+
+ if(ret==-1){
+ end=bisect;
+ }else{
+ int64_t frameno=ogg_page_frameno(&og);
+ if(frameno<target){
+ best=ret; /* raw offset of packet with frameno */
+ begin=vf->offset; /* raw offset of next packet */
+ }else{
+ end=bisect;
+ }
+ }
+ }
+
+ /* found our page. seek to it (call raw_seek). */
+
+ if(ov_raw_seek(vf,best))goto seek_error;
+ }
+
+ /* verify result */
+ if(vf->pcm_offset>=pos)goto seek_error;
+ if(pos>ov_pcm_total(vf,-1))goto seek_error;
+
+ /* discard samples until we reach the desired position. Crossing a
+ logical bitstream boundary with abandon is OK. */
+ while(vf->pcm_offset<pos){
+ double **pcm;
+ long target=pos-vf->pcm_offset;
+ long samples=vorbis_synthesis_pcmout(&vf->vd,&pcm);
+
+ if(samples>target)samples=target;
+ vorbis_synthesis_read(&vf->vd,samples);
+ vf->pcm_offset+=samples;
+
+ if(samples<target)
+ if(_process_packet(vf,1)==0)
+ vf->pcm_offset=ov_pcm_total(vf,-1); /* eof */
+ }
+ return 0;
+
+ seek_error:
+ /* dump machine so we're in a known state */
+ vf->pcm_offset=-1;
+ _decode_clear(vf);
+ return -1;
+}
+
+/* seek to a playback time relative to the decompressed pcm stream
+ returns zero on success, nonzero on failure */
+int ov_time_seek(OggVorbis_File *vf,double seconds){
+ /* translate time to PCM position and call ov_pcm_seek */
+
+ int link=-1;
+ int64_t pcm_total=ov_pcm_total(vf,-1);
+ double time_total=ov_time_total(vf,-1);
+
+ if(!vf->seekable)return(-1); /* don't dump machine if we can't seek */
+ if(seconds<0 || seconds>time_total)goto seek_error;
+
+ /* which bitstream section does this time offset occur in? */
+ for(link=vf->links-1;link>=0;link--){
+ pcm_total-=vf->pcmlengths[link];
+ time_total-=ov_time_total(vf,link);
+ if(seconds>=time_total)break;
+ }
+
+ /* enough information to convert time offset to pcm offset */
+ {
+ int64_t target=pcm_total+(seconds-time_total)*vf->vi[link].rate;
+ return(ov_pcm_seek(vf,target));
+ }
+
+ seek_error:
+ /* dump machine so we're in a known state */
+ vf->pcm_offset=-1;
+ _decode_clear(vf);
+ return -1;
+}
+
+/* tell the current stream offset cursor. Note that seek followed by
+ tell will likely not give the set offset due to caching */
+int64_t ov_raw_tell(OggVorbis_File *vf){
+ return(vf->offset);
+}
+
+/* return PCM offset (sample) of next PCM sample to be read */
+int64_t ov_pcm_tell(OggVorbis_File *vf){
+ return(vf->pcm_offset);
+}
+
+/* return time offset (seconds) of next PCM sample to be read */
+double ov_time_tell(OggVorbis_File *vf){
+ /* translate time to PCM position and call ov_pcm_seek */
+
+ int link=-1;
+ int64_t pcm_total=0;
+ double time_total=0.;
+
+ if(vf->seekable){
+ pcm_total=ov_pcm_total(vf,-1);
+ time_total=ov_time_total(vf,-1);
+
+ /* which bitstream section does this time offset occur in? */
+ for(link=vf->links-1;link>=0;link--){
+ pcm_total-=vf->pcmlengths[link];
+ time_total-=ov_time_total(vf,link);
+ if(vf->pcm_offset>=pcm_total)break;
+ }
+ }
+
+ return((double)time_total+(double)(vf->pcm_offset-pcm_total)/vf->vi[link].rate);
+}
+
+/* link: -1) return the vorbis_info struct for the bitstream section
+ currently being decoded
+ 0-n) to request information for a specific bitstream section
+
+ In the case of a non-seekable bitstream, any call returns the
+ current bitstream. NULL in the case that the machine is not
+ initialized */
+
+vorbis_info *ov_info(OggVorbis_File *vf,int link){
+ if(vf->seekable){
+ if(link<0)
+ if(vf->decode_ready)
+ return vf->vi+vf->current_link;
+ else
+ return NULL;
+ else
+ if(link>=vf->links)
+ return NULL;
+ else
+ return vf->vi+link;
+ }else{
+ if(vf->decode_ready)
+ return vf->vi;
+ else
+ return NULL;
+ }
+}
+
+/* grr, strong typing, grr, no templates/inheritence, grr */
+vorbis_comment *ov_comment(OggVorbis_File *vf,int link){
+ if(vf->seekable){
+ if(link<0)
+ if(vf->decode_ready)
+ return vf->vc+vf->current_link;
+ else
+ return NULL;
+ else
+ if(link>=vf->links)
+ return NULL;
+ else
+ return vf->vc+link;
+ }else{
+ if(vf->decode_ready)
+ return vf->vc;
+ else
+ return NULL;
+ }
+}
+
+int host_is_big_endian() {
+ short pattern = 0xbabe;
+ unsigned char *bytewise = (unsigned char *)&pattern;
+ if (bytewise[0] == 0xba) return 1;
+
+ assert(bytewise[0] == 0xbe);
+ return 0;
+}
+
+/* up to this point, everything could more or less hide the multiple
+ logical bitstream nature of chaining from the toplevel application
+ if the toplevel application didn't particularly care. However, at
+ the point that we actually read audio back, the multiple-section
+ nature must surface: Multiple bitstream sections do not necessarily
+ have to have the same number of channels or sampling rate.
+
+ ov_read returns the sequential logical bitstream number currently
+ being decoded along with the PCM data in order that the toplevel
+ application can take action on channel/sample rate changes. This
+ number will be incremented even for streamed (non-seekable) streams
+ (for seekable streams, it represents the actual logical bitstream
+ index within the physical bitstream. Note that the accessor
+ functions above are aware of this dichotomy).
+
+ input values: buffer) a buffer to hold packed PCM data for return
+ length) the byte length requested to be placed into buffer
+ bigendianp) should the data be packed LSB first (0) or
+ MSB first (1)
+ word) word size for output. currently 1 (byte) or
+ 2 (16 bit short)
+
+ return values: -1) error/hole in data
+ 0) EOF
+ n) number of bytes of PCM actually returned. The
+ below works on a packet-by-packet basis, so the
+ return length is not related to the 'length' passed
+ in, just guaranteed to fit.
+
+ *section) set to the logical bitstream number */
+
+long ov_read(OggVorbis_File *vf,char *buffer,int length,
+ int bigendianp,int word,int sgned,int *bitstream){
+ int i,j;
+ int host_endian = host_is_big_endian();
+
+ while(1){
+ if(vf->decode_ready){
+ double **pcm;
+ long samples=vorbis_synthesis_pcmout(&vf->vd,&pcm);
+ if(samples){
+ /* yay! proceed to pack data into the byte buffer */
+
+ long channels=ov_info(vf,-1)->channels;
+ long bytespersample=word * channels;
+ if(samples>length/bytespersample)samples=length/bytespersample;
+
+ /* a tight loop to pack each size */
+ {
+ int val;
+ if(word==1){
+ int off=(sgned?0:128);
+ for(j=0;j<samples;j++)
+ for(i=0;i<channels;i++){
+ val=(int)(pcm[i][j]*128. + 0.5);
+ if(val>127)val=127;
+ else if(val<-128)val=-128;
+ *buffer++=val+off;
+ }
+ }else{
+ int off=(sgned?0:32768);
+
+ if(host_endian==bigendianp){
+ if(sgned){
+ for(i=0;i<channels;i++) { /* It's faster in this order */
+ double *src=pcm[i];
+ short *dest=((short *)buffer)+i;
+ for(j=0;j<samples;j++) {
+ val=(int)(src[j]*32768. + 0.5);
+ if(val>32767)val=32767;
+ else if(val<-32768)val=-32768;
+ *dest=val;
+ dest+=channels;
+ }
+ }
+ }else{
+ for(i=0;i<channels;i++) {
+ double *src=pcm[i];
+ short *dest=((short *)buffer)+i;
+ for(j=0;j<samples;j++) {
+ val=(int)(src[j]*32768. + 0.5);
+ if(val>32767)val=32767;
+ else if(val<-32768)val=-32768;
+ *dest=val+off;
+ dest+=channels;
+ }
+ }
+ }
+ }else if(bigendianp){
+ for(j=0;j<samples;j++)
+ for(i=0;i<channels;i++){
+ val=(int)(pcm[i][j]*32768. + 0.5);
+ if(val>32767)val=32767;
+ else if(val<-32768)val=-32768;
+ val+=off;
+ *buffer++=(val>>8);
+ *buffer++=(val&0xff);
+ }
+ }else{
+ int val;
+ for(j=0;j<samples;j++)
+ for(i=0;i<channels;i++){
+ val=(int)(pcm[i][j]*32768. + 0.5);
+ if(val>32767)val=32767;
+ else if(val<-32768)val=-32768;
+ val+=off;
+ *buffer++=(val&0xff);
+ *buffer++=(val>>8);
+ }
+
+ }
+ }
+ }
+
+ vorbis_synthesis_read(&vf->vd,samples);
+ vf->pcm_offset+=samples;
+ if(bitstream)*bitstream=vf->current_link;
+ return(samples*bytespersample);
+ }
+ }
+
+ /* suck in another packet */
+ switch(_process_packet(vf,1)){
+ case 0:
+ return(0);
+ case -1:
+ return -1;
+ default:
+ break;
+ }
+ }
+}
+
+
+
+