Full 'vorbisfile.a' library commit. The whole convenience API is
authorMonty <xiphmont@xiph.org>
Thu, 4 Nov 1999 12:37:56 +0000 (12:37 +0000)
committerMonty <xiphmont@xiph.org>
Thu, 4 Nov 1999 12:37:56 +0000 (12:37 +0000)
there; the vast majority of the code is untested.  chaining_example.c
does work, however.

Monty

svn path=/trunk/vorbis/; revision=160

lib/chaining_example.c
lib/codec.h
lib/vorbisfile.c
lib/vorbisfile.h

index f686f91..24f9b97 100644 (file)
@@ -32,25 +32,24 @@ int main(){
   }
   
   /* print details about each logical bitstream in the input */
-  if(ov.seekable){
+  if(ov_seekable(&ov)){
     printf("Input bitstream contained %d logical bitstream section(s).\n",
-          ov.links);
+          ov_streams(&ov));
     printf("Total bitstream playing time: %ld seconds\n\n",
-          (long)ov_totaltime(&ov));
+          (long)ov_time_total(&ov,-1));
 
   }else{
     printf("Standard input was not seekable.\n"
           "First logical bitstream information:\n\n");
   }
 
-  for(i=0;i<ov.links;i++){
+  for(i=0;i<ov_streams(&ov);i++){
+    vorbis_info *vi=ov_info(&ov,i);
     printf("\tlogical bitstream section %d information:\n",i+1);
     printf("\t\t%ldHz %d channels serial number=%ld\n",
-          ov.vi[i].rate,ov.vi[i].channels,ov.serialnos[i]);
-    printf("\t\tcompressed length: %ldbytes ",(ov.offsets?
-                                    ov.offsets[i+1]-ov.offsets[i]:
-                                    -1));
-    printf(" play time: %lds\n",(long)ov_lbtime(&ov,i));
+          vi->rate,vi->channels,ov.serialnos[i]);
+    printf("\t\tcompressed length: %ldbytes ",ov_raw_total(&ov,i));
+    printf(" play time: %lds\n",(long)ov_time_total(&ov,i));
   }
   
   ov_clear(&ov);
index 412c1e1..c6df51a 100644 (file)
@@ -172,7 +172,7 @@ typedef struct {
 
 
   int    *lacing_vals;    /* The values that will go to the segment table */
-  int64_t *pcm_vals;      /* pcm_pos values for headers. Not compact
+  size64 *pcm_vals;      /* pcm_pos values for headers. Not compact
                             this way, but it is simple coupled to the
                             lacing fifo */
   long    lacing_storage;
@@ -194,7 +194,7 @@ typedef struct {
                              but we need coupling so that the codec
                              (which is in a seperate abstraction
                              layer) also knows about the gap */
-  int64_t  pcmpos;
+  size64   pcmpos;
 
 } ogg_stream_state;
 
@@ -207,7 +207,7 @@ typedef struct {
   long  b_o_s;
   long  e_o_s;
 
-  int64_t frameno;
+  size64  frameno;
   long    packetno;       /* sequence number for decode; the framing
                              knows where there's a hole in the data,
                              but we need coupling so that the codec
@@ -346,7 +346,7 @@ extern int     ogg_page_version(ogg_page *og);
 extern int     ogg_page_continued(ogg_page *og);
 extern int     ogg_page_bos(ogg_page *og);
 extern int     ogg_page_eos(ogg_page *og);
-extern int64_t ogg_page_frameno(ogg_page *og);
+extern size64  ogg_page_frameno(ogg_page *og);
 extern int     ogg_page_serialno(ogg_page *og);
 extern int     ogg_page_pageno(ogg_page *og);
 
index cc3e5bd..59ef54f 100644 (file)
@@ -14,7 +14,7 @@
  function: stdio-based convenience library for opening/seeking/decoding
  author: Monty <xiphmont@mit.edu>
  modifications by: Monty
- last modification date: Nov 02 1999
+ last modification date: Nov 04 1999
 
  ********************************************************************/
 
@@ -344,22 +344,134 @@ static int _open_seekable(OggVorbis_File *vf){
 }
 
 static int _open_nonseekable(OggVorbis_File *vf){
-  vorbis_info vi;
-  long serialno;
-
-  /* Try to fetch the headers, maintaining all the storage */
-  if(_fetch_headers(vf,&vi,&serialno)==-1)return(-1);
-  
   /* we cannot seek. Set up a 'single' (current) logical bitstream entry  */
   vf->links=1;
   vf->vi=malloc(sizeof(vorbis_info));
-  vf->serialnos=malloc(sizeof(long));
-  
-  memcpy(vf->vi,&vi,sizeof(vorbis_info));
-  vf->serialnos[0]=serialno;
+
+  /* Try to fetch the headers, maintaining all the storage */
+  if(_fetch_headers(vf,vf->vi,&vf->current_serialno)==-1)return(-1);
+    
   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->pcm_offset=-1;
+  vf->decode_ready=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);
+      size64 frameno;
+      
+      if(result==-1)return(-1); /* hole in the data. alert the toplevel */
+      if(result>0){
+       /* got a packet.  process it */
+       frameno=op.frameno;
+       vorbis_synthesis(&vf->vb,&op);
+       vorbis_synthesis_blockin(&vf->vd,&vf->vb);
+       
+       /* update the pcm offset. */
+       if(frameno!=-1){
+         int link=(vf->seekable?vf->current_link:0);
+         double **dummy;
+         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* */
+         
+         samples=vorbis_synthesis_pcmout(&vf->vd,&dummy);
+         
+         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 */
+
+    /* 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;
+      }else{
+       /* we're streaming */
+       /* fetch the three header packets, build the info struct */
+       
+       _fetch_headers(vf,vf->vi,&vf->current_serialno);
+       vf->current_link++;
+       link=0;
+      }
+      
+      /* reload */
+      ogg_stream_init(&vf->os,vf->current_serialno);
+      vorbis_synthesis_init(&vf->vd,vf->vi+link);
+      vorbis_block_init(&vf->vd,&vf->vb);
+      vf->decode_ready=1;
+    }
+    ogg_stream_pagein(&vf->os,&og);
+  }
+}
+
 /**********************************************************************
  * The helpers are over; it's all toplevel interface from here on out */
  
@@ -418,55 +530,407 @@ int ov_open(FILE *f,OggVorbis_File *vf,char *initial,long ibytes){
   if(ret){
     vf->f=NULL;
     ov_clear(vf);
+  }else{
+    ogg_stream_init(&vf->os,vf->current_serialno);
+    vorbis_synthesis_init(&vf->vd,vf->vi);
+    vorbis_block_init(&vf->vd,&vf->vb);
+    vf->decode_ready=1;
   }
   return(ret);
 }
 
-double ov_lbtime(OggVorbis_File *vf,int i){
-  if(i>=0 && i<vf->links)
-    return((float)(vf->pcmlengths[i])/vf->vi[i].rate);
-  return(0);
+long ov_streams(OggVorbis_File *vf){
+  return vf->links;
 }
 
-double ov_totaltime(OggVorbis_File *vf){
-  double acc=0;
-  int i;
-  for(i=0;i<vf->links;i++)
-    acc+=ov_lbtime(vf,i);
-  return(acc);
+long ov_seekable(OggVorbis_File *vf){
+  return vf->seekable;
 }
 
-/* seek to an offset relative to the *compressed* data */
-int ov_seek_stream(OggVorbis_File *vf,long pos){
+long ov_raw_total(OggVorbis_File *vf,int i){
+  if(!vf->seekable)return(-1);
+  if(i<0 || i>=vf->links){
+    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]);
+  }
+}
 
+size64 ov_pcm_total(OggVorbis_File *vf,int i){
+  if(!vf->seekable)return(-1);
+  if(i<0 || i>=vf->links){
+    size64 acc=0;
+    int i;
+    for(i=0;i<vf->links;i++)
+      acc+=ov_pcm_total(vf,i);
+    return(acc);
+  }else{
+    return(vf->pcmlengths[i]);
+  }
+}
 
+double ov_time_total(OggVorbis_File *vf,int i){
+  if(!vf->seekable)return(-1);
+  if(i<0 || i>=vf->links){
+    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). */
+
+int ov_raw_seek(OggVorbis_File *vf,long pos){
+  int link;
+
+  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 */
+  _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 */
+  _decode_clear(vf);
+  return -1;
 }
 
-/* seek to the beginning of the next logical bitstream within the
-   physical bitstream */
-int ov_seek_bitstream(OggVorbis_File *vf,long pos){
+/* seek to a sample offset relative to the decompressed pcm stream */
+int ov_pcm_seek(OggVorbis_File *vf,size64 pos){
+  int i,link=-1;
+  size64 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;
+  }
+
+  /* seach 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) */
+  {
+    size64 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,acc;
+    
+      if(end-begin<CHUNKSIZE){
+       bisect=begin;
+      }else{
+       bisect=(end+begin)/2;
+      }
+    
+      _seek_helper(vf,bisect);
+
+      acc=0;
+      while(1){
+       ret=_get_next_page(vf,&og,-1);
+       
+       if(ret==-1){
+         end=bisect;
+       }else{
+         size64 frameno=ogg_page_frameno(&og);
+         acc+=ret;
+         if(frameno==-1)continue;
+         if(frameno<target){
+           best=bisect+acc;  /* raw offset of packet with frameno */ 
+           begin=vf->offset; /* raw offset of next packet */
+         }else{
+           end=bisect;
+         }
+       }
+       break;
+      }
+    }
 
+    /* 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 */
+  _decode_clear(vf);
+  return -1;
 }
 
-/* seek to an offset relative to the decompressed *output* stream */
-int ov_seek_pcm(OggVorbis_File *vf,long pos){
+/* seek to a playback time relative to the decompressed pcm stream */
 
+int ov_time_seek(OggVorbis_File *vf,double seconds){
+  /* translate time to PCM position and call ov_pcm_seek */
 
+  int i,link=-1;
+  size64 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 */
+  {
+    size64 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 */
+  _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 */
+long ov_raw_tell(OggVorbis_File *vf){
+  return(vf->offset);
 }
 
-int ov_seek_time(OggVorbis_File *vf,double seconds){
+size64 ov_pcm_tell(OggVorbis_File *vf){
+  return(vf->pcm_offset);
+}
 
+double ov_time_tell(OggVorbis_File *vf){
+  /* translate time to PCM position and call ov_pcm_seek */
 
+  int link=-1;
+  size64 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(time_total+(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;
+  }
+}
 
+/* 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;
+
+  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 */
+       {
+         if(word==1){
+           int off=(sgned?0:128);
+           for(j=0;j<samples;j++)
+             for(i=0;i<channels;i++){
+               int val=rint(pcm[i][j]*128.);
+               if(val>127)val=127;
+               if(val<-128)val=-128;
+               *buffer++=val+off;
+             }
+         }else{
+           int off=(sgned?0:32768);
+
+           if(bigendianp){
+             for(j=0;j<samples;j++)
+               for(i=0;i<channels;i++){
+                 int val=rint(pcm[i][j]*32768.);
+                 if(val>32767)val=32767;
+                 if(val<-32768)val=-32768;
+                 val+=off;
+                 *buffer++=(val>>8);
+                 *buffer++=(val&0xff);
+               }
+           }else{
+             for(j=0;j<samples;j++)
+               for(i=0;i<channels;i++){
+                 int val=rint(pcm[i][j]*32768.);
+                 if(val>32767)val=32767;
+                 if(val<-32768)val=-32768;
+                 val+=off;
+                 *buffer++=(val&0xff);
+                 *buffer++=(val>>8);
+               }
+
+           }
+         }
+       }
+       
+       vorbis_synthesis_read(&vf->vd,samples);
+       vf->pcm_offset+=samples;
+       *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;
+    }
+  }
+}
 
 
 
index 3bdb14a..0502de2 100644 (file)
  function: stdio-based convenience library for opening/seeking/decoding
  author: Monty <xiphmont@mit.edu>
  modifications by: Monty
- last modification date: Nov 02 1999
+ last modification date: Nov 04 1999
 
  ********************************************************************/
 
-#ifndef _VO_FILE_H_
-#define _VO_FILE_H_
+#ifndef _OV_FILE_H_
+#define _OV_FILE_H_
 
 #include <stdio.h>
 #include "codec.h"
@@ -40,7 +40,11 @@ typedef struct {
   vorbis_info      *vi;
 
   /* Decoding working state local storage */
-  int ready;
+  size64           pcm_offset;
+  int              decode_ready;
+  long             current_serialno;
+  int              current_link;
+
   ogg_stream_state os; /* take physical pages, weld into a logical
                           stream of packets */
   vorbis_dsp_state vd; /* central working state for the packet->PCM decoder */
@@ -51,9 +55,25 @@ typedef struct {
 extern int ov_clear(OggVorbis_File *vf);
 extern int ov_open(FILE *f,OggVorbis_File *vf,char *initial,long ibytes);
 
-extern double ov_lbtime(OggVorbis_File *vf,int i);
-extern double ov_totaltime(OggVorbis_File *vf);
+extern long ov_streams(OggVorbis_File *vf);
+extern long ov_seekable(OggVorbis_File *vf);
+
+extern long ov_raw_total(OggVorbis_File *vf,int i);
+extern size64 ov_pcm_total(OggVorbis_File *vf,int i);
+extern double ov_time_total(OggVorbis_File *vf,int i);
+
+extern int ov_raw_seek(OggVorbis_File *vf,long pos);
+extern int ov_pcm_seek(OggVorbis_File *vf,size64 pos);
+extern int ov_time_seek(OggVorbis_File *vf,double pos);
+
+extern long ov_raw_tell(OggVorbis_File *vf);
+extern size64 ov_pcm_tell(OggVorbis_File *vf);
+extern double ov_time_tell(OggVorbis_File *vf);
+
+extern vorbis_info *ov_info(OggVorbis_File *vf,int link);
 
+extern long ov_read(OggVorbis_File *vf,char *buffer,int length,
+                   int bigendianp,int word,int sgned,int *bitstream);
 
 #endif