diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/analysis.c | 105 | ||||
| -rw-r--r-- | lib/framing.c | 1623 | ||||
| -rw-r--r-- | lib/info.c | 543 | ||||
| -rw-r--r-- | lib/mapping0.c | 383 | ||||
| -rw-r--r-- | lib/psy.c | 110 | ||||
| -rw-r--r-- | lib/psytune.c | 64 | ||||
| -rw-r--r-- | lib/res0.c | 384 | ||||
| -rw-r--r-- | lib/vorbisfile.c | 1156 |
8 files changed, 4261 insertions, 107 deletions
diff --git a/lib/analysis.c b/lib/analysis.c new file mode 100644 index 00000000..54735277 --- /dev/null +++ b/lib/analysis.c @@ -0,0 +1,105 @@ +/******************************************************************** + * * + * 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: single-block PCM analysis mode dispatch + last mod: $Id: analysis.c,v 1.31.2.1 2000/08/15 08:33:43 xiphmont Exp $ + + ********************************************************************/ + +#include <stdio.h> +#include <string.h> +#include <math.h> +#include "vorbis/codec.h" +#include "bitwise.h" +#include "registry.h" +#include "scales.h" +#include "os.h" + +/* decides between modes, dispatches to the appropriate mapping. */ +int vorbis_analysis(vorbis_block *vb,ogg_packet *op){ + vorbis_dsp_state *vd=vb->vd; + vorbis_info *vi=vd->vi; + int type; + int mode=0; + + vb->glue_bits=0; + vb->time_bits=0; + vb->floor_bits=0; + vb->res_bits=0; + + /* first things first. Make sure encode is ready */ + _oggpack_reset(&vb->opb); + /* Encode the packet type */ + _oggpack_write(&vb->opb,0,1); + + /* currently lazy. Short block dispatches to 0, long to 1. */ + + if(vb->W &&vi->modes>1)mode=1; + type=vi->map_type[vi->mode_param[mode]->mapping]; + vb->mode=mode; + + /* Encode frame mode, pre,post windowsize, then dispatch */ + _oggpack_write(&vb->opb,mode,vd->modebits); + if(vb->W){ + _oggpack_write(&vb->opb,vb->lW,1); + _oggpack_write(&vb->opb,vb->nW,1); + /*fprintf(stderr,"*"); + }else{ + fprintf(stderr,".");*/ + } + + if(_mapping_P[type]->forward(vb,vd->mode[mode])) + return(-1); + + /* set up the packet wrapper */ + + op->packet=_oggpack_buffer(&vb->opb); + op->bytes=_oggpack_bytes(&vb->opb); + op->b_o_s=0; + op->e_o_s=vb->eofflag; + op->frameno=vb->frameno; + op->packetno=vb->sequence; /* for sake of completeness */ + + return(0); +} + +/* there was no great place to put this.... */ +void _analysis_output(char *base,int i,double *v,int n,int bark,int dB){ +#ifdef ANALYSIS + int j; + FILE *of; + char buffer[80]; + sprintf(buffer,"%s_%d.m",base,i); + of=fopen(buffer,"w"); + + if(!of)perror("failed to open data dump file"); + + for(j=0;j<n;j++){ + if(dB && v[j]==0) + fprintf(of,"\n\n"); + else{ + if(bark) + fprintf(of,"%g ",toBARK(22050.*j/n)); + else + fprintf(of,"%g ",(double)j); + + if(dB){ + fprintf(of,"%g\n",todB(fabs(v[j]))); + }else{ + fprintf(of,"%g\n",v[j]); + } + } + } + fclose(of); +#endif +} diff --git a/lib/framing.c b/lib/framing.c new file mode 100644 index 00000000..60bd5bcc --- /dev/null +++ b/lib/framing.c @@ -0,0 +1,1623 @@ +/******************************************************************** + * * + * 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: code raw [Vorbis] packets into framed OggSquish stream and + decode Ogg streams back into raw packets + last mod: $Id: framing.c,v 1.21.2.1 2000/08/15 08:33:43 xiphmont Exp $ + + note: The CRC code is directly derived from public domain code by + Ross Williams (ross@guest.adelaide.edu.au). See docs/framing.html + for details. + + ********************************************************************/ + +#include <stdlib.h> +#include <string.h> +#include "vorbis/codec.h" +#include "misc.h" + +/* A complete description of Ogg framing exists in docs/framing.html */ + +int ogg_page_version(ogg_page *og){ + return((int)(og->header[4])); +} + +int ogg_page_continued(ogg_page *og){ + return((int)(og->header[5]&0x01)); +} + +int ogg_page_bos(ogg_page *og){ + return((int)(og->header[5]&0x02)); +} + +int ogg_page_eos(ogg_page *og){ + return((int)(og->header[5]&0x04)); +} + +int64_t ogg_page_frameno(ogg_page *og){ + unsigned char *page=og->header; + int64_t pcmpos=page[13]&(0xff); + pcmpos= (pcmpos<<8)|(page[12]&0xff); + pcmpos= (pcmpos<<8)|(page[11]&0xff); + pcmpos= (pcmpos<<8)|(page[10]&0xff); + pcmpos= (pcmpos<<8)|(page[9]&0xff); + pcmpos= (pcmpos<<8)|(page[8]&0xff); + pcmpos= (pcmpos<<8)|(page[7]&0xff); + pcmpos= (pcmpos<<8)|(page[6]&0xff); + return(pcmpos); +} + +int ogg_page_serialno(ogg_page *og){ + return(og->header[14] | + (og->header[15]<<8) | + (og->header[16]<<16) | + (og->header[17]<<24)); +} + +int ogg_page_pageno(ogg_page *og){ + return(og->header[18] | + (og->header[19]<<8) | + (og->header[20]<<16) | + (og->header[21]<<24)); +} + +/* helper to initialize lookup for direct-table CRC */ + +static unsigned vorbis_size32_t crc_lookup[256]; +static int crc_ready=0; + +static unsigned vorbis_size32_t _ogg_crc_entry(unsigned long index){ + int i; + unsigned long r; + + r = index << 24; + for (i=0; i<8; i++) + if (r & 0x80000000UL) + r = (r << 1) ^ 0x04c11db7; /* The same as the ethernet generator + polynomial, although we use an + unreflected alg and an init/final + of 0, not 0xffffffff */ + else + r<<=1; + return (r & 0xffffffffUL); +} + +/* mind this in threaded code; sync_init and stream_init call it. + It's thread safe only after the first time it returns */ + +static void _ogg_crc_init(void){ + if(!crc_ready){ + /* initialize the crc_lookup table */ + int i; + for (i=0;i<256;i++) + crc_lookup[i]=_ogg_crc_entry((unsigned long)i); + crc_ready=0; + } +} + +/* init the encode/decode logical stream state */ + +int ogg_stream_init(ogg_stream_state *os,int serialno){ + if(os){ + memset(os,0,sizeof(ogg_stream_state)); + os->body_storage=16*1024; + os->body_data=malloc(os->body_storage*sizeof(char)); + + os->lacing_storage=1024; + os->lacing_vals=malloc(os->lacing_storage*sizeof(int)); + os->pcm_vals=malloc(os->lacing_storage*sizeof(int64_t)); + + /* initialize the crc_lookup table if not done */ + _ogg_crc_init(); + + os->serialno=serialno; + + return(0); + } + return(-1); +} + +/* _clear does not free os, only the non-flat storage within */ +int ogg_stream_clear(ogg_stream_state *os){ + if(os){ + if(os->body_data)free(os->body_data); + if(os->lacing_vals)free(os->lacing_vals); + if(os->pcm_vals)free(os->pcm_vals); + + memset(os,0,sizeof(ogg_stream_state)); + } + return(0); +} + +int ogg_stream_destroy(ogg_stream_state *os){ + if(os){ + ogg_stream_clear(os); + free(os); + } + return(0); +} + +/* Helpers for ogg_stream_encode; this keeps the structure and + what's happening fairly clear */ + +static void _os_body_expand(ogg_stream_state *os,int needed){ + if(os->body_storage<=os->body_fill+needed){ + os->body_storage+=(needed+1024); + os->body_data=realloc(os->body_data,os->body_storage); + } +} + +static void _os_lacing_expand(ogg_stream_state *os,int needed){ + if(os->lacing_storage<=os->lacing_fill+needed){ + os->lacing_storage+=(needed+32); + os->lacing_vals=realloc(os->lacing_vals,os->lacing_storage*sizeof(int)); + os->pcm_vals=realloc(os->pcm_vals,os->lacing_storage*sizeof(int64_t)); + } +} + +/* checksum the page */ +/* Direct table CRC; note that this will be faster in the future if we + perform the checksum silmultaneously with other copies */ + +static void _os_checksum(ogg_page *og){ + unsigned vorbis_size32_t crc_reg=0; + int i; + + for(i=0;i<og->header_len;i++) + crc_reg=(crc_reg<<8)^crc_lookup[((crc_reg >> 24)&0xff)^og->header[i]]; + for(i=0;i<og->body_len;i++) + crc_reg=(crc_reg<<8)^crc_lookup[((crc_reg >> 24)&0xff)^og->body[i]]; + + og->header[22]=crc_reg&0xff; + og->header[23]=(crc_reg>>8)&0xff; + og->header[24]=(crc_reg>>16)&0xff; + og->header[25]=(crc_reg>>24)&0xff; +} + +/* submit data to the internal buffer of the framing engine */ +int ogg_stream_packetin(ogg_stream_state *os,ogg_packet *op){ + int lacing_vals=op->bytes/255+1,i; + + if(os->body_returned){ + /* advance packet data according to the body_returned pointer. We + had to keep it around to return a pointer into the buffer last + call */ + + os->body_fill-=os->body_returned; + if(os->body_fill) + memmove(os->body_data,os->body_data+os->body_returned, + os->body_fill*sizeof(char)); + os->body_returned=0; + } + + /* make sure we have the buffer storage */ + _os_body_expand(os,op->bytes); + _os_lacing_expand(os,lacing_vals); + + /* Copy in the submitted packet. Yes, the copy is a waste; this is + the liability of overly clean abstraction for the time being. It + will actually be fairly easy to eliminate the extra copy in the + future */ + + memcpy(os->body_data+os->body_fill,op->packet,op->bytes); + os->body_fill+=op->bytes; + + /* Store lacing vals for this packet */ + for(i=0;i<lacing_vals-1;i++){ + os->lacing_vals[os->lacing_fill+i]=255; + os->pcm_vals[os->lacing_fill+i]=os->pcmpos; + } + os->lacing_vals[os->lacing_fill+i]=(op->bytes)%255; + os->pcmpos=os->pcm_vals[os->lacing_fill+i]=op->frameno; + + /* flag the first segment as the beginning of the packet */ + os->lacing_vals[os->lacing_fill]|= 0x100; + + os->lacing_fill+=lacing_vals; + + /* for the sake of completeness */ + os->packetno++; + + if(op->e_o_s)os->e_o_s=1; + + return(0); +} + +/* This will flush remaining packets into a page (returning nonzero), + even if there is not enough data to trigger a flush normally + (undersized page). If there are no packets or partial packets to + flush, ogg_stream_flush returns 0. Note that ogg_stream_flush will + try to flush a normal sized page like ogg_stream_pageout; a call to + ogg_stream_flush does not gurantee that all packets have flushed. + Only a return value of 0 from ogg_stream_flush indicates all packet + data is flushed into pages. + + ogg_stream_page will flush the last page in a stream even if it's + undersized; you almost certainly want to use ogg_stream_pageout + (and *not* ogg_stream_flush) unless you need to flush an undersized + page in the middle of a stream for some reason. */ + +int ogg_stream_flush(ogg_stream_state *os,ogg_page *og){ + int i; + int vals=0; + int maxvals=(os->lacing_fill>255?255:os->lacing_fill); + int bytes=0; + long acc=0; + int64_t pcm_pos=os->pcm_vals[0]; + + if(maxvals==0)return(0); + + /* construct a page */ + /* decide how many segments to include */ + + /* If this is the initial header case, the first page must only include + the initial header packet */ + if(os->b_o_s==0){ /* 'initial header page' case */ + pcm_pos=0; + for(vals=0;vals<maxvals;vals++){ + if((os->lacing_vals[vals]&0x0ff)<255){ + vals++; + break; + } + } + }else{ + for(vals=0;vals<maxvals;vals++){ + if(acc>4096)break; + acc+=os->lacing_vals[vals]&0x0ff; + pcm_pos=os->pcm_vals[vals]; + } + } + + /* construct the header in temp storage */ + memcpy(os->header,"OggS",4); + + /* stream structure version */ + os->header[4]=0x00; + + /* continued packet flag? */ + os->header[5]=0x00; + if((os->lacing_vals[0]&0x100)==0)os->header[5]|=0x01; + /* first page flag? */ + if(os->b_o_s==0)os->header[5]|=0x02; + /* last page flag? */ + if(os->e_o_s && os->lacing_fill==vals)os->header[5]|=0x04; + os->b_o_s=1; + + /* 64 bits of PCM position */ + for(i=6;i<14;i++){ + os->header[i]=(pcm_pos&0xff); + pcm_pos>>=8; + } + + /* 32 bits of stream serial number */ + { + long serialno=os->serialno; + for(i=14;i<18;i++){ + os->header[i]=(serialno&0xff); + serialno>>=8; + } + } + + /* 32 bits of page counter (we have both counter and page header + because this val can roll over) */ + if(os->pageno==-1)os->pageno=0; /* because someone called + stream_reset; this would be a + strange thing to do in an + encode stream, but it has + plausible uses */ + { + long pageno=os->pageno++; + for(i=18;i<22;i++){ + os->header[i]=(pageno&0xff); + pageno>>=8; + } + } + + /* zero for computation; filled in later */ + os->header[22]=0; + os->header[23]=0; + os->header[24]=0; + os->header[25]=0; + + /* segment table */ + os->header[26]=vals&0xff; + for(i=0;i<vals;i++) + bytes+=os->header[i+27]=(os->lacing_vals[i]&0xff); + + /* set pointers in the ogg_page struct */ + og->header=os->header; + og->header_len=os->header_fill=vals+27; + og->body=os->body_data+os->body_returned; + og->body_len=bytes; + + /* advance the lacing data and set the body_returned pointer */ + + os->lacing_fill-=vals; + memmove(os->lacing_vals,os->lacing_vals+vals,os->lacing_fill*sizeof(int)); + memmove(os->pcm_vals,os->pcm_vals+vals,os->lacing_fill*sizeof(int64_t)); + os->body_returned+=bytes; + + /* calculate the checksum */ + + _os_checksum(og); + + /* done */ + return(1); +} + + +/* This constructs pages from buffered packet segments. The pointers +returned are to static buffers; do not free. The returned buffers are +good only until the next call (using the same ogg_stream_state) */ + +int ogg_stream_pageout(ogg_stream_state *os, ogg_page *og){ + + if((os->e_o_s&&os->lacing_fill) || /* 'were done, now flush' case */ + os->body_fill-os->body_returned > 4096 ||/* 'page nominal size' case */ + os->lacing_fill>=255 || /* 'segment table full' case */ + (os->lacing_fill&&!os->b_o_s)){ /* 'initial header page' case */ + + return(ogg_stream_flush(os,og)); + } + + /* not enough data to construct a page and not end of stream */ + return(0); +} + +int ogg_stream_eof(ogg_stream_state *os){ + return os->e_o_s; +} + +/* DECODING PRIMITIVES: packet streaming layer **********************/ + +/* This has two layers to place more of the multi-serialno and paging + control in the application's hands. First, we expose a data buffer + using ogg_sync_buffer(). The app either copies into the + buffer, or passes it directly to read(), etc. We then call + ogg_sync_wrote() to tell how many bytes we just added. + + Pages are returned (pointers into the buffer in ogg_sync_state) + by ogg_sync_pageout(). The page is then submitted to + ogg_stream_pagein() along with the appropriate + ogg_stream_state* (ie, matching serialno). We then get raw + packets out calling ogg_stream_packetout() with a + ogg_stream_state. See the 'frame-prog.txt' docs for details and + example code. */ + +/* initialize the struct to a known state */ +int ogg_sync_init(ogg_sync_state *oy){ + if(oy){ + memset(oy,0,sizeof(ogg_sync_state)); + _ogg_crc_init(); + } + return(0); +} + +/* clear non-flat storage within */ +int ogg_sync_clear(ogg_sync_state *oy){ + if(oy){ + if(oy->data)free(oy->data); + ogg_sync_init(oy); + } + return(0); +} + +char *ogg_sync_buffer(ogg_sync_state *oy, long size){ + + /* first, clear out any space that has been previously returned */ + if(oy->returned){ + oy->fill-=oy->returned; + if(oy->fill>0) + memmove(oy->data,oy->data+oy->returned, + (oy->fill)*sizeof(char)); + oy->returned=0; + } + + if(size>oy->storage-oy->fill){ + /* We need to extend the internal buffer */ + long newsize=size+oy->fill+4096; /* an extra page to be nice */ + + if(oy->data) + oy->data=realloc(oy->data,newsize); + else + oy->data=malloc(newsize); + oy->storage=newsize; + } + + /* expose a segment at least as large as requested at the fill mark */ + return((char *)oy->data+oy->fill); +} + +int ogg_sync_wrote(ogg_sync_state *oy, long bytes){ + if(oy->fill+bytes>oy->storage)return(-1); + oy->fill+=bytes; + return(0); +} + +/* sync the stream. This is meant to be useful for finding page + boundaries. + + return values for this: + -n) skipped n bytes + 0) page not ready; more data (no bytes skipped) + n) page synced at current location; page length n bytes + +*/ + +long ogg_sync_pageseek(ogg_sync_state *oy,ogg_page *og){ + unsigned char *page=oy->data+oy->returned; + unsigned char *next; + long bytes=oy->fill-oy->returned; + + if(oy->headerbytes==0){ + int headerbytes,i; + if(bytes<27)return(0); /* not enough for a header */ + + /* verify capture pattern */ + if(memcmp(page,"OggS",4))goto sync_fail; + + headerbytes=page[26]+27; + if(bytes<headerbytes)return(0); /* not enough for header + seg table */ + + /* count up body length in the segment table */ + + for(i=0;i<page[26];i++) + oy->bodybytes+=page[27+i]; + oy->headerbytes=headerbytes; + } + + if(oy->bodybytes+oy->headerbytes>bytes)return(0); + + /* The whole test page is buffered. Verify the checksum */ + { + /* Grab the checksum bytes, set the header field to zero */ + char chksum[4]; + ogg_page log; + + memcpy(chksum,page+22,4); + memset(page+22,0,4); + + /* set up a temp page struct and recompute the checksum */ + log.header=page; + log.header_len=oy->headerbytes; + log.body=page+oy->headerbytes; + log.body_len=oy->bodybytes; + _os_checksum(&log); + + /* Compare */ + if(memcmp(chksum,page+22,4)){ + /* D'oh. Mismatch! Corrupt page (or miscapture and not a page + at all) */ + /* replace the computed checksum with the one actually read in */ + memcpy(page+22,chksum,4); + + /* Bad checksum. Lose sync */ + goto sync_fail; + } + } + + /* yes, have a whole page all ready to go */ + { + unsigned char *page=oy->data+oy->returned; + long bytes; + + if(og){ + og->header=page; + og->header_len=oy->headerbytes; + og->body=page+oy->headerbytes; + og->body_len=oy->bodybytes; + } + + oy->unsynced=0; + oy->returned+=(bytes=oy->headerbytes+oy->bodybytes); + oy->headerbytes=0; + oy->bodybytes=0; + return(bytes); + } + + sync_fail: + + oy->headerbytes=0; + oy->bodybytes=0; + + /* search for possible capture */ + next=memchr(page+1,'O',bytes-1); + if(!next) + next=oy->data+oy->fill; + + oy->returned=next-oy->data; + return(-(next-page)); +} + +/* sync the stream and get a page. Keep trying until we find a page. + Supress 'sync errors' after reporting the first. + + return values: + -1) recapture (hole in data) + 0) need more data + 1) page returned + + Returns pointers into buffered data; invalidated by next call to + _stream, _clear, _init, or _buffer */ + +int ogg_sync_pageout(ogg_sync_state *oy, ogg_page *og){ + + /* all we need to do is verify a page at the head of the stream + buffer. If it doesn't verify, we look for the next potential + frame */ + + while(1){ + long ret=ogg_sync_pageseek(oy,og); + if(ret>0){ + /* have a page */ + return(1); + } + if(ret==0){ + /* need more data */ + return(0); + } + + /* head did not start a synced page... skipped some bytes */ + if(!oy->unsynced){ + oy->unsynced=1; + return(-1); + } + + /* loop. keep looking */ + + } +} + +/* add the incoming page to the stream state; we decompose the page + into packet segments here as well. */ + +int ogg_stream_pagein(ogg_stream_state *os, ogg_page *og){ + unsigned char *header=og->header; + unsigned char *body=og->body; + long bodysize=og->body_len; + int segptr=0; + + int version=ogg_page_version(og); + int continued=ogg_page_continued(og); + int bos=ogg_page_bos(og); + int eos=ogg_page_eos(og); + int64_t pcmpos=ogg_page_frameno(og); + int serialno=ogg_page_serialno(og); + int pageno=ogg_page_pageno(og); + int segments=header[26]; + + /* clean up 'returned data' */ + { + long lr=os->lacing_returned; + long br=os->body_returned; + + /* body data */ + if(br){ + os->body_fill-=br; + if(os->body_fill) + memmove(os->body_data,os->body_data+br,os->body_fill); + os->body_returned=0; + } + + if(lr){ + /* segment table */ + if(os->lacing_fill-lr){ + memmove(os->lacing_vals,os->lacing_vals+lr, + (os->lacing_fill-lr)*sizeof(int)); + memmove(os->pcm_vals,os->pcm_vals+lr, + (os->lacing_fill-lr)*sizeof(int64_t)); + } + os->lacing_fill-=lr; + os->lacing_packet-=lr; + os->lacing_returned=0; + } + } + + /* check the serial number */ + if(serialno!=os->serialno)return(-1); + if(version>0)return(-1); + + _os_lacing_expand(os,segments+1); + + /* are we in sequence? */ + if(pageno!=os->pageno){ + int i; + + /* unroll previous partial packet (if any) */ + for(i=os->lacing_packet;i<os->lacing_fill;i++) + os->body_fill-=os->lacing_vals[i]&0xff; + os->lacing_fill=os->lacing_packet; + + /* make a note of dropped data in segment table */ + if(os->pageno!=-1){ + os->lacing_vals[os->lacing_fill++]=0x400; + os->lacing_packet++; + } + + /* are we a 'continued packet' page? If so, we'll need to skip + some segments */ + if(continued){ + bos=0; + for(;segptr<segments;segptr++){ + int val=header[27+segptr]; + body+=val; + bodysize-=val; + if(val<255){ + segptr++; + break; + } + } + } + } + + if(bodysize){ + _os_body_expand(os,bodysize); + memcpy(os->body_data+os->body_fill,body,bodysize); + os->body_fill+=bodysize; + } + + { + int saved=-1; + while(segptr<segments){ + int val=header[27+segptr]; + os->lacing_vals[os->lacing_fill]=val; + os->pcm_vals[os->lacing_fill]=-1; + + if(bos){ + os->lacing_vals[os->lacing_fill]|=0x100; + bos=0; + } + + if(val<255)saved=os->lacing_fill; + + os->lacing_fill++; + segptr++; + + if(val<255)os->lacing_packet=os->lacing_fill; + } + + /* set the pcmpos on the last pcmval of the last full packet */ + if(saved!=-1){ + os->pcm_vals[saved]=pcmpos; + } + + } + + if(eos){ + os->e_o_s=1; + if(os->lacing_fill>0) + os->lacing_vals[os->lacing_fill-1]|=0x200; + } + + os->pageno=pageno+1; + + return(0); +} + +/* clear things to an initial state. Good to call, eg, before seeking */ +int ogg_sync_reset(ogg_sync_state *oy){ + oy->fill=0; + oy->returned=0; + oy->unsynced=0; + oy->headerbytes=0; + oy->bodybytes=0; + return(0); +} + +int ogg_stream_reset(ogg_stream_state *os){ + os->body_fill=0; + os->body_returned=0; + + os->lacing_fill=0; + os->lacing_packet=0; + os->lacing_returned=0; + + os->header_fill=0; + + os->e_o_s=0; + os->b_o_s=0; + os->pageno=-1; + os->packetno=0; + os->pcmpos=0; + + return(0); +} + +int ogg_stream_packetout(ogg_stream_state *os,ogg_packet *op){ + + /* The last part of decode. We have the stream broken into packet + segments. Now we need to group them into packets (or return the + out of sync markers) */ + + int ptr=os->lacing_returned; + + if(os->lacing_packet<=ptr)return(0); + + if(os->lacing_vals[ptr]&0x400){ + /* We lost sync here; let the app know */ + os->lacing_returned++; + + /* we need to tell the codec there's a gap; it might need to + handle previous packet dependencies. */ + os->packetno++; + return(-1); + } + + /* Gather the whole packet. We'll have no holes or a partial packet */ + { + int size=os->lacing_vals[ptr]&0xff; + int bytes=0; + + op->packet=os->body_data+os->body_returned; + op->e_o_s=os->lacing_vals[ptr]&0x200; /* last packet of the stream? */ + op->b_o_s=os->lacing_vals[ptr]&0x100; /* first packet of the stream? */ + bytes+=size; + + while(size==255){ + int val=os->lacing_vals[++ptr]; + size=val&0xff; + if(val&0x200)op->e_o_s=0x200; + bytes+=size; + } + + op->packetno=os->packetno; + op->frameno=os->pcm_vals[ptr]; + op->bytes=bytes; + + os->body_returned+=bytes; + os->lacing_returned=ptr+1; + } + os->packetno++; + return(1); +} + +#ifdef _V_SELFTEST +#include <stdio.h> + +ogg_stream_state os_en, os_de; +ogg_sync_state oy; + +void checkpacket(ogg_packet *op,int len, int no, int pos){ + long j; + static int sequence=0; + static int lastno=0; + + if(op->bytes!=len){ + fprintf(stderr,"incorrect packet length!\n"); + exit(1); + } + if(op->frameno!=pos){ + fprintf(stderr,"incorrect packet position!\n"); + exit(1); + } + + /* packet number just follows sequence/gap; adjust the input number + for that */ + if(no==0){ + sequence=0; + }else{ + sequence++; + if(no>lastno+1) + sequence++; + } + lastno=no; + if(op->packetno!=sequence){ + fprintf(stderr,"incorrect packet sequence %ld != %d\n", + (long)(op->packetno),sequence); + exit(1); + } + + /* Test data */ + for(j=0;j<op->bytes;j++) + if(op->packet[j]!=((j+no)&0xff)){ + fprintf(stderr,"body data mismatch (1) at pos %ld: %x!=%lx!\n\n", + j,op->packet[j],(j+no)&0xff); + exit(1); + } +} + +void check_page(unsigned char *data,const int *header,ogg_page *og){ + long j; + /* Test data */ + for(j=0;j<og->body_len;j++) + if(og->body[j]!=data[j]){ + fprintf(stderr,"body data mismatch (2) at pos %ld: %x!=%x!\n\n", + j,data[j],og->body[j]); + exit(1); + } + + /* Test header */ + for(j=0;j<og->header_len;j++){ + if(og->header[j]!=header[j]){ + fprintf(stderr,"header content mismatch at pos %ld:\n",j); + for(j=0;j<header[26]+27;j++) + fprintf(stderr," (%ld)%02x:%02x",j,header[j],og->header[j]); + fprintf(stderr,"\n"); + exit(1); + } + } + if(og->header_len!=header[26]+27){ + fprintf(stderr,"header length incorrect! (%ld!=%d)\n", + og->header_len,header[26]+27); + exit(1); + } +} + +void print_header(ogg_page *og){ + int j; + fprintf(stderr,"\nHEADER:\n"); + fprintf(stderr," capture: %c %c %c %c version: %d flags: %x\n", + og->header[0],og->header[1],og->header[2],og->header[3], + (int)og->header[4],(int)og->header[5]); + + fprintf(stderr," pcmpos: %d serialno: %d pageno: %d\n", + (og->header[9]<<24)|(og->header[8]<<16)| + (og->header[7]<<8)|og->header[6], + (og->header[17]<<24)|(og->header[16]<<16)| + (og->header[15]<<8)|og->header[14], + (og->header[21]<<24)|(og->header[20]<<16)| + (og->header[19]<<8)|og->header[18]); + + fprintf(stderr," checksum: %02x:%02x:%02x:%02x\n segments: %d (", + (int)og->header[22],(int)og->header[23], + (int)og->header[24],(int)og->header[25], + (int)og->header[26]); + + for(j=27;j<og->header_len;j++) + fprintf(stderr,"%d ",(int)og->header[j]); + fprintf(stderr,")\n\n"); +} + +void copy_page(ogg_page *og){ + unsigned char *temp=malloc(og->header_len); + memcpy(temp,og->header,og->header_len); + og->header=temp; + + temp=malloc(og->body_len); + memcpy(temp,og->body,og->body_len); + og->body=temp; +} + +void error(void){ + fprintf(stderr,"error!\n"); + exit(1); +} + +/* 17 only */ +const int head1_0[] = {0x4f,0x67,0x67,0x53,0,0x06, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0x15,0xed,0xec,0x91, + 1, + 17}; + +/* 17, 254, 255, 256, 500, 510, 600 byte, pad */ +const int head1_1[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0x59,0x10,0x6c,0x2c, + 1, + 17}; +const int head2_1[] = {0x4f,0x67,0x67,0x53,0,0x04, + 0x07,0x18,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0x89,0x33,0x85,0xce, + 13, + 254,255,0,255,1,255,245,255,255,0, + 255,255,90}; + +/* nil packets; beginning,middle,end */ +const int head1_2[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0xff,0x7b,0x23,0x17, + 1, + 0}; +const int head2_2[] = {0x4f,0x67,0x67,0x53,0,0x04, + 0x07,0x28,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0x5c,0x3f,0x66,0xcb, + 17, + 17,254,255,0,0,255,1,0,255,245,255,255,0, + 255,255,90,0}; + +/* large initial packet */ +const int head1_3[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0x01,0x27,0x31,0xaa, + 18, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,10}; + +const int head2_3[] = {0x4f,0x67,0x67,0x53,0,0x04, + 0x07,0x08,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0x7f,0x4e,0x8a,0xd2, + 4, + 255,4,255,0}; + + +/* continuing packet test */ +const int head1_4[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0xff,0x7b,0x23,0x17, + 1, + 0}; + +const int head2_4[] = {0x4f,0x67,0x67,0x53,0,0x00, + 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0x34,0x24,0xd5,0x29, + 17, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255}; + +const int head3_4[] = {0x4f,0x67,0x67,0x53,0,0x05, + 0x07,0x0c,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,2,0,0,0, + 0xc8,0xc3,0xcb,0xed, + 5, + 10,255,4,255,0}; + + +/* page with the 255 segment limit */ +const int head1_5[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0xff,0x7b,0x23,0x17, + 1, + 0}; + +const int head2_5[] = {0x4f,0x67,0x67,0x53,0,0x00, + 0x07,0xfc,0x03,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0xed,0x2a,0x2e,0xa7, + 255, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10}; + +const int head3_5[] = {0x4f,0x67,0x67,0x53,0,0x04, + 0x07,0x00,0x04,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,2,0,0,0, + 0x6c,0x3b,0x82,0x3d, + 1, + 50}; + + +/* packet that overspans over an entire page */ +const int head1_6[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0xff,0x7b,0x23,0x17, + 1, + 0}; + +const int head2_6[] = {0x4f,0x67,0x67,0x53,0,0x00, + 0x07,0x04,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0x3c,0xd9,0x4d,0x3f, + 17, + 100,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255}; + +const int head3_6[] = {0x4f,0x67,0x67,0x53,0,0x01, + 0x07,0x04,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,2,0,0,0, + 0xbd,0xd5,0xb5,0x8b, + 17, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255}; + +const int head4_6[] = {0x4f,0x67,0x67,0x53,0,0x05, + 0x07,0x10,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,3,0,0,0, + 0xef,0xdd,0x88,0xde, + 7, + 255,255,75,255,4,255,0}; + +/* packet that overspans over an entire page */ +const int head1_7[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0xff,0x7b,0x23,0x17, + 1, + 0}; + +const int head2_7[] = {0x4f,0x67,0x67,0x53,0,0x00, + 0x07,0x04,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0x3c,0xd9,0x4d,0x3f, + 17, + 100,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255}; + +const int head3_7[] = {0x4f,0x67,0x67,0x53,0,0x05, + 0x07,0x08,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,2,0,0,0, + 0xd4,0xe0,0x60,0xe5, + 1,0}; + +void test_pack(const int *pl, const int **headers){ + unsigned char *data=malloc(1024*1024); /* for scripted test cases only */ + long inptr=0; + long outptr=0; + long deptr=0; + long depacket=0; + long pcm_pos=7; + int i,j,packets,pageno=0,pageout=0; + int eosflag=0; + int bosflag=0; + + ogg_stream_reset(&os_en); + ogg_stream_reset(&os_de); + ogg_sync_reset(&oy); + + for(packets=0;;packets++)if(pl[packets]==-1)break; + + for(i=0;i<packets;i++){ + /* construct a test packet */ + ogg_packet op; + int len=pl[i]; + + op.packet=data+inptr; + op.bytes=len; + op.e_o_s=(pl[i+1]<0?1:0); + op.frameno=pcm_pos; + + pcm_pos+=1024; + + for(j=0;j<len;j++)data[inptr++]=i+j; + + /* submit the test packet */ + ogg_stream_packetin(&os_en,&op); + + /* retrieve any finished pages */ + { + ogg_page og; + + while(ogg_stream_pageout(&os_en,&og)){ + /* We have a page. Check it carefully */ + + fprintf(stderr,"%d, ",pageno); + + if(headers[pageno]==NULL){ + fprintf(stderr,"coded too many pages!\n"); + exit(1); + } + + check_page(data+outptr,headers[pageno],&og); + + outptr+=og.body_len; + pageno++; + + /* have a complete page; submit it to sync/decode */ + + { + ogg_page og_de; + ogg_packet op_de; + char *buf=ogg_sync_buffer(&oy,og.header_len+og.body_len); + memcpy(buf,og.header,og.header_len); + memcpy(buf+og.header_len,og.body,og.body_len); + ogg_sync_wrote(&oy,og.header_len+og.body_len); + + while(ogg_sync_pageout(&oy,&og_de)>0){ + /* got a page. Happy happy. Verify that it's good. */ + + check_page(data+deptr,headers[pageout],&og_de); + deptr+=og_de.body_len; + pageout++; + + /* submit it to deconstitution */ + ogg_stream_pagein(&os_de,&og_de); + + /* packets out? */ + while(ogg_stream_packetout(&os_de,&op_de)>0){ + + /* verify the packet! */ + /* check data */ + if(memcmp(data+depacket,op_de.packet,op_de.bytes)){ + fprintf(stderr,"packet data mismatch in decode! pos=%ld\n", + depacket); + exit(1); + } + /* check bos flag */ + if(bosflag==0 && op_de.b_o_s==0){ + fprintf(stderr,"b_o_s flag not set on packet!\n"); + exit(1); + } + if(bosflag && op_de.b_o_s){ + fprintf(stderr,"b_o_s flag incorrectly set on packet!\n"); + exit(1); + } + bosflag=1; + depacket+=op_de.bytes; + + /* check eos flag */ + if(eosflag){ + fprintf(stderr,"Multiple decoded packets with eos flag!\n"); + exit(1); + } + + if(op_de.e_o_s)eosflag=1; + + /* check pcmpos flag */ + if(op_de.frameno!=-1){ + fprintf(stderr," pcm:%ld ",(long)op_de.frameno); + } + } + } + } + } + } + } + free(data); + if(headers[pageno]!=NULL){ + fprintf(stderr,"did not write last page!\n"); + exit(1); + } + if(headers[pageout]!=NULL){ + fprintf(stderr,"did not decode last page!\n"); + exit(1); + } + if(inptr!=outptr){ + fprintf(stderr,"encoded page data incomplete!\n"); + exit(1); + } + if(inptr!=deptr){ + fprintf(stderr,"decoded page data incomplete!\n"); + exit(1); + } + if(inptr!=depacket){ + fprintf(stderr,"decoded packet data incomplete!\n"); + exit(1); + } + if(!eosflag){ + fprintf(stderr,"Never got a packet with EOS set!\n"); + exit(1); + } + fprintf(stderr,"ok.\n"); +} + +int main(void){ + + ogg_stream_init(&os_en,0x04030201); + ogg_stream_init(&os_de,0x04030201); + ogg_sync_init(&oy); + + /* Exercise each code path in the framing code. Also verify that + the checksums are working. */ + + { + /* 17 only */ + const int packets[]={17, -1}; + const int *headret[]={head1_0,NULL}; + + fprintf(stderr,"testing single page encoding... "); + test_pack(packets,headret); + } + + { + /* 17, 254, 255, 256, 500, 510, 600 byte, pad */ + const int packets[]={17, 254, 255, 256, 500, 510, 600, -1}; + const int *headret[]={head1_1,head2_1,NULL}; + + fprintf(stderr,"testing basic page encoding... "); + test_pack(packets,headret); + } + + { + /* nil packets; beginning,middle,end */ + const int packets[]={0,17, 254, 255, 0, 256, 0, 500, 510, 600, 0, -1}; + const int *headret[]={head1_2,head2_2,NULL}; + + fprintf(stderr,"testing basic nil packets... "); + test_pack(packets,headret); + } + + { + /* large initial packet */ + const int packets[]={4345,259,255,-1}; + const int *headret[]={head1_3,head2_3,NULL}; + + fprintf(stderr,"testing initial-packet lacing > 4k... "); + test_pack(packets,headret); + } + + { + /* continuing packet test */ + const int packets[]={0,4345,259,255,-1}; + const int *headret[]={head1_4,head2_4,head3_4,NULL}; + + fprintf(stderr,"testing single packet page span... "); + test_pack(packets,headret); + } + + /* page with the 255 segment limit */ + { + + const int packets[]={0,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,50,-1}; + const int *headret[]={head1_5,head2_5,head3_5,NULL}; + + fprintf(stderr,"testing max packet segments... "); + test_pack(packets,headret); + } + + { + /* packet that overspans over an entire page */ + const int packets[]={0,100,9000,259,255,-1}; + const int *headret[]={head1_6,head2_6,head3_6,head4_6,NULL}; + + fprintf(stderr,"testing very large packets... "); + test_pack(packets,headret); + } + + { + /* term only page. why not? */ + const int packets[]={0,100,4080,-1}; + const int *headret[]={head1_7,head2_7,head3_7,NULL}; + + fprintf(stderr,"testing zero data page (1 nil packet)... "); + test_pack(packets,headret); + } + + + + { + /* build a bunch of pages for testing */ + unsigned char *data=malloc(1024*1024); + int pl[]={0,100,4079,2956,2057,76,34,912,0,234,1000,1000,1000,300,-1}; + int inptr=0,i,j; + ogg_page og[5]; + + ogg_stream_reset(&os_en); + + for(i=0;pl[i]!=-1;i++){ + ogg_packet op; + int len=pl[i]; + + op.packet=data+inptr; + op.bytes=len; + op.e_o_s=(pl[i+1]<0?1:0); + op.frameno=(i+1)*1000; + + for(j=0;j<len;j++)data[inptr++]=i+j; + ogg_stream_packetin(&os_en,&op); + } + + free(data); + + /* retrieve finished pages */ + for(i=0;i<5;i++){ + if(ogg_stream_pageout(&os_en,&og[i])==0){ + fprintf(stderr,"Too few pages output building sync tests!\n"); + exit(1); + } + copy_page(&og[i]); + } + + /* Test lost pages on pagein/packetout: no rollback */ + { + ogg_page temp; + ogg_packet test; + + fprintf(stderr,"Testing loss of pages... "); + + ogg_sync_reset(&oy); + ogg_stream_reset(&os_de); + for(i=0;i<5;i++){ + memcpy(ogg_sync_buffer(&oy,og[i].header_len),og[i].header, + og[i].header_len); + ogg_sync_wrote(&oy,og[i].header_len); + memcpy(ogg_sync_buffer(&oy,og[i].body_len),og[i].body,og[i].body_len); + ogg_sync_wrote(&oy,og[i].body_len); + } + + ogg_sync_pageout(&oy,&temp); + ogg_stream_pagein(&os_de,&temp); + ogg_sync_pageout(&oy,&temp); + ogg_stream_pagein(&os_de,&temp); + ogg_sync_pageout(&oy,&temp); + /* skip */ + ogg_sync_pageout(&oy,&temp); + ogg_stream_pagein(&os_de,&temp); + + /* do we get the expected results/packets? */ + + if(ogg_stream_packetout(&os_de,&test)!=1)error(); + checkpacket(&test,0,0,0); + if(ogg_stream_packetout(&os_de,&test)!=1)error(); + checkpacket(&test,100,1,-1); + if(ogg_stream_packetout(&os_de,&test)!=1)error(); + checkpacket(&test,4079,2,3000); + if(ogg_stream_packetout(&os_de,&test)!=-1){ + fprintf(stderr,"Error: loss of page did not return error\n"); + exit(1); + } + if(ogg_stream_packetout(&os_de,&test)!=1)error(); + checkpacket(&test,76,5,-1); + if(ogg_stream_packetout(&os_de,&test)!=1)error(); + checkpacket(&test,34,6,-1); + fprintf(stderr,"ok.\n"); + } + + /* Test lost pages on pagein/packetout: rollback with continuation */ + { + ogg_page temp; + ogg_packet test; + + fprintf(stderr,"Testing loss of pages (rollback required)... "); + + ogg_sync_reset(&oy); + ogg_stream_reset(&os_de); + for(i=0;i<5;i++){ + memcpy(ogg_sync_buffer(&oy,og[i].header_len),og[i].header, + og[i].header_len); + ogg_sync_wrote(&oy,og[i].header_len); + memcpy(ogg_sync_buffer(&oy,og[i].body_len),og[i].body,og[i].body_len); + ogg_sync_wrote(&oy,og[i].body_len); + } + + ogg_sync_pageout(&oy,&temp); + ogg_stream_pagein(&os_de,&temp); + ogg_sync_pageout(&oy,&temp); + ogg_stream_pagein(&os_de,&temp); + ogg_sync_pageout(&oy,&temp); + ogg_stream_pagein(&os_de,&temp); + ogg_sync_pageout(&oy,&temp); + /* skip */ + ogg_sync_pageout(&oy,&temp); + ogg_stream_pagein(&os_de,&temp); + + /* do we get the expected results/packets? */ + + if(ogg_stream_packetout(&os_de,&test)!=1)error(); + checkpacket(&test,0,0,0); + if(ogg_stream_packetout(&os_de,&test)!=1)error(); + checkpacket(&test,100,1,-1); + if(ogg_stream_packetout(&os_de,&test)!=1)error(); + checkpacket(&test,4079,2,3000); + if(ogg_stream_packetout(&os_de,&test)!=1)error(); + checkpacket(&test,2956,3,4000); + if(ogg_stream_packetout(&os_de,&test)!=-1){ + fprintf(stderr,"Error: loss of page did not return error\n"); + exit(1); + } + if(ogg_stream_packetout(&os_de,&test)!=1)error(); + checkpacket(&test,300,13,14000); + fprintf(stderr,"ok.\n"); + } + + /* the rest only test sync */ + { + ogg_page og_de; + /* Test fractional page inputs: incomplete capture */ + fprintf(stderr,"Testing sync on partial inputs... "); + ogg_sync_reset(&oy); + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header, + 3); + ogg_sync_wrote(&oy,3); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + + /* Test fractional page inputs: incomplete fixed header */ + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header+3, + 20); + ogg_sync_wrote(&oy,20); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + + /* Test fractional page inputs: incomplete header */ + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header+23, + 5); + ogg_sync_wrote(&oy,5); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + + /* Test fractional page inputs: incomplete body */ + + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header+28, + og[1].header_len-28); + ogg_sync_wrote(&oy,og[1].header_len-28); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + + memcpy(ogg_sync_buffer(&oy,og[1].body_len),og[1].body,1000); + ogg_sync_wrote(&oy,1000); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + + memcpy(ogg_sync_buffer(&oy,og[1].body_len),og[1].body+1000, + og[1].body_len-1000); + ogg_sync_wrote(&oy,og[1].body_len-1000); + if(ogg_sync_pageout(&oy,&og_de)<=0)error(); + + fprintf(stderr,"ok.\n"); + } + + /* Test fractional page inputs: page + incomplete capture */ + { + ogg_page og_de; + fprintf(stderr,"Testing sync on 1+partial inputs... "); + ogg_sync_reset(&oy); + + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header, + og[1].header_len); + ogg_sync_wrote(&oy,og[1].header_len); + + memcpy(ogg_sync_buffer(&oy,og[1].body_len),og[1].body, + og[1].body_len); + ogg_sync_wrote(&oy,og[1].body_len); + + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header, + 20); + ogg_sync_wrote(&oy,20); + if(ogg_sync_pageout(&oy,&og_de)<=0)error(); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header+20, + og[1].header_len-20); + ogg_sync_wrote(&oy,og[1].header_len-20); + memcpy(ogg_sync_buffer(&oy,og[1].body_len),og[1].body, + og[1].body_len); + ogg_sync_wrote(&oy,og[1].body_len); + if(ogg_sync_pageout(&oy,&og_de)<=0)error(); + + fprintf(stderr,"ok.\n"); + } + + /* Test recapture: garbage + page */ + { + ogg_page og_de; + fprintf(stderr,"Testing search for capture... "); + ogg_sync_reset(&oy); + + /* 'garbage' */ + memcpy(ogg_sync_buffer(&oy,og[1].body_len),og[1].body, + og[1].body_len); + ogg_sync_wrote(&oy,og[1].body_len); + + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header, + og[1].header_len); + ogg_sync_wrote(&oy,og[1].header_len); + + memcpy(ogg_sync_buffer(&oy,og[1].body_len),og[1].body, + og[1].body_len); + ogg_sync_wrote(&oy,og[1].body_len); + + memcpy(ogg_sync_buffer(&oy,og[2].header_len),og[2].header, + 20); + ogg_sync_wrote(&oy,20); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + if(ogg_sync_pageout(&oy,&og_de)<=0)error(); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + + memcpy(ogg_sync_buffer(&oy,og[2].header_len),og[2].header+20, + og[2].header_len-20); + ogg_sync_wrote(&oy,og[2].header_len-20); + memcpy(ogg_sync_buffer(&oy,og[2].body_len),og[2].body, + og[2].body_len); + ogg_sync_wrote(&oy,og[2].body_len); + if(ogg_sync_pageout(&oy,&og_de)<=0)error(); + + fprintf(stderr,"ok.\n"); + } + + /* Test recapture: page + garbage + page */ + { + ogg_page og_de; + fprintf(stderr,"Testing recapture... "); + ogg_sync_reset(&oy); + + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header, + og[1].header_len); + ogg_sync_wrote(&oy,og[1].header_len); + + memcpy(ogg_sync_buffer(&oy,og[1].body_len),og[1].body, + og[1].body_len); + ogg_sync_wrote(&oy,og[1].body_len); + + memcpy(ogg_sync_buffer(&oy,og[2].header_len),og[2].header, + og[2].header_len); + ogg_sync_wrote(&oy,og[2].header_len); + + memcpy(ogg_sync_buffer(&oy,og[2].header_len),og[2].header, + og[2].header_len); + ogg_sync_wrote(&oy,og[2].header_len); + + if(ogg_sync_pageout(&oy,&og_de)<=0)error(); + + memcpy(ogg_sync_buffer(&oy,og[2].body_len),og[2].body, + og[2].body_len-5); + ogg_sync_wrote(&oy,og[2].body_len-5); + + memcpy(ogg_sync_buffer(&oy,og[3].header_len),og[3].header, + og[3].header_len); + ogg_sync_wrote(&oy,og[3].header_len); + + memcpy(ogg_sync_buffer(&oy,og[3].body_len),og[3].body, + og[3].body_len); + ogg_sync_wrote(&oy,og[3].body_len); + + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + if(ogg_sync_pageout(&oy,&og_de)<=0)error(); + + fprintf(stderr,"ok.\n"); + } + } + + return(0); +} + +#endif + + + + diff --git a/lib/info.c b/lib/info.c new file mode 100644 index 00000000..c04080eb --- /dev/null +++ b/lib/info.c @@ -0,0 +1,543 @@ +/******************************************************************** + * * + * 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: maintain the info structure, info <-> header packets + last mod: $Id: info.c,v 1.25.2.1 2000/08/15 08:33:44 xiphmont Exp $ + + ********************************************************************/ + +/* general handling of the header and the vorbis_info structure (and + substructures) */ + +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include "vorbis/codec.h" +#include "vorbis/backends.h" +#include "bitwise.h" +#include "sharedbook.h" +#include "bookinternal.h" +#include "registry.h" +#include "window.h" +#include "psy.h" +#include "misc.h" +#include "os.h" + +/* helpers */ +static int ilog2(unsigned int v){ + int ret=0; + while(v>1){ + ret++; + v>>=1; + } + return(ret); +} + +static void _v_writestring(oggpack_buffer *o,char *s){ + while(*s){ + _oggpack_write(o,*s++,8); + } +} + +static void _v_readstring(oggpack_buffer *o,char *buf,int bytes){ + while(bytes--){ + *buf++=_oggpack_read(o,8); + } +} + +void vorbis_comment_init(vorbis_comment *vc){ + memset(vc,0,sizeof(vorbis_comment)); +} + +void vorbis_comment_add(vorbis_comment *vc,char *comment){ + vc->user_comments=realloc(vc->user_comments, + (vc->comments+2)*sizeof(char *)); + vc->comment_lengths=realloc(vc->comment_lengths, + (vc->comments+2)*sizeof(int)); + vc->user_comments[vc->comments]=strdup(comment); + vc->comment_lengths[vc->comments]=strlen(comment); + vc->comments++; + vc->user_comments[vc->comments]=NULL; +} + +void vorbis_comment_add_tag(vorbis_comment *vc, char *tag, char *contents){ + char *comment=alloca(strlen(tag)+strlen(contents)+2); /* +2 for = and \0 */ + strcpy(comment, tag); + strcat(comment, "="); + strcat(comment, contents); + vorbis_comment_add(vc, comment); +} + +/* This is more or less the same as strncasecmp - but that doesn't exist + * everywhere, and this is a fairly trivial function, so we include it */ +static int tagcompare(const char *s1, const char *s2, int n){ + int c=0; + while(c < n){ + if(toupper(s1[c]) != toupper(s2[c])) + return !0; + c++; + } + return 0; +} + +char *vorbis_comment_query(vorbis_comment *vc, char *tag, int count){ + long i; + int found = 0; + int taglen = strlen(tag); + char *fulltag = alloca(taglen+ 2); + + strcpy(fulltag, tag); + strcat(fulltag, "="); + + for(i=0;i<vc->comments;i++){ + if(!tagcompare(vc->user_comments[i], fulltag, taglen)){ + if(count == found) + /* We return a pointer to the data, not a copy */ + return vc->user_comments[i] + taglen + 1; + else + found++; + } + } + return NULL; /* didn't find anything */ +} + +void vorbis_comment_clear(vorbis_comment *vc){ + if(vc){ + long i; + for(i=0;i<vc->comments;i++) + if(vc->user_comments[i])free(vc->user_comments[i]); + if(vc->user_comments)free(vc->user_comments); + if(vc->comment_lengths)free(vc->comment_lengths); + if(vc->vendor)free(vc->vendor); + } + memset(vc,0,sizeof(vorbis_comment)); +} + +/* used by synthesis, which has a full, alloced vi */ +void vorbis_info_init(vorbis_info *vi){ + memset(vi,0,sizeof(vorbis_info)); +} + +void vorbis_info_clear(vorbis_info *vi){ + int i; + + for(i=0;i<vi->modes;i++) + if(vi->mode_param[i])free(vi->mode_param[i]); + /*if(vi->mode_param)free(vi->mode_param);*/ + + for(i=0;i<vi->maps;i++) /* unpack does the range checking */ + _mapping_P[vi->map_type[i]]->free_info(vi->map_param[i]); + /*if(vi->map_param)free(vi->map_param);*/ + + for(i=0;i<vi->times;i++) /* unpack does the range checking */ + _time_P[vi->time_type[i]]->free_info(vi->time_param[i]); + /*if(vi->time_param)free(vi->time_param);*/ + + for(i=0;i<vi->floors;i++) /* unpack does the range checking */ + _floor_P[vi->floor_type[i]]->free_info(vi->floor_param[i]); + /*if(vi->floor_param)free(vi->floor_param);*/ + + for(i=0;i<vi->residues;i++) /* unpack does the range checking */ + _residue_P[vi->residue_type[i]]->free_info(vi->residue_param[i]); + /*if(vi->residue_param)free(vi->residue_param);*/ + + /* the static codebooks *are* freed if you call info_clear, because + decode side does alloc a 'static' codebook. Calling clear on the + full codebook does not clear the static codebook (that's our + responsibility) */ + for(i=0;i<vi->books;i++){ + /* just in case the decoder pre-cleared to save space */ + if(vi->book_param[i]){ + vorbis_staticbook_clear(vi->book_param[i]); + free(vi->book_param[i]); + } + } + /*if(vi->book_param)free(vi->book_param);*/ + + for(i=0;i<vi->psys;i++) + _vi_psy_free(vi->psy_param[i]); + /*if(vi->psy_param)free(vi->psy_param);*/ + + memset(vi,0,sizeof(vorbis_info)); +} + +/* Header packing/unpacking ********************************************/ + +static int _vorbis_unpack_info(vorbis_info *vi,oggpack_buffer *opb){ + vi->version=_oggpack_read(opb,32); + if(vi->version!=0)return(-1); + + vi->channels=_oggpack_read(opb,8); + vi->rate=_oggpack_read(opb,32); + + vi->bitrate_upper=_oggpack_read(opb,32); + vi->bitrate_nominal=_oggpack_read(opb,32); + vi->bitrate_lower=_oggpack_read(opb,32); + + vi->blocksizes[0]=1<<_oggpack_read(opb,4); + vi->blocksizes[1]=1<<_oggpack_read(opb,4); + + if(vi->rate<1)goto err_out; + if(vi->channels<1)goto err_out; + if(vi->blocksizes[0]<8)goto err_out; + if(vi->blocksizes[1]<vi->blocksizes[0])goto err_out; + + if(_oggpack_read(opb,1)!=1)goto err_out; /* EOP check */ + + return(0); + err_out: + vorbis_info_clear(vi); + return(-1); +} + +static int _vorbis_unpack_comment(vorbis_comment *vc,oggpack_buffer *opb){ + int i; + int vendorlen=_oggpack_read(opb,32); + if(vendorlen<0)goto err_out; + vc->vendor=calloc(vendorlen+1,1); + _v_readstring(opb,vc->vendor,vendorlen); + vc->comments=_oggpack_read(opb,32); + if(vc->comments<0)goto err_out; + vc->user_comments=calloc(vc->comments+1,sizeof(char **)); + vc->comment_lengths=calloc(vc->comments+1, sizeof(int)); + + for(i=0;i<vc->comments;i++){ + int len=_oggpack_read(opb,32); + if(len<0)goto err_out; + vc->comment_lengths[i]=len; + vc->user_comments[i]=calloc(len+1,1); + _v_readstring(opb,vc->user_comments[i],len); + } + if(_oggpack_read(opb,1)!=1)goto err_out; /* EOP check */ + + return(0); + err_out: + vorbis_comment_clear(vc); + return(-1); +} + +/* all of the real encoding details are here. The modes, books, + everything */ +static int _vorbis_unpack_books(vorbis_info *vi,oggpack_buffer *opb){ + int i; + + /* codebooks */ + vi->books=_oggpack_read(opb,8)+1; + /*vi->book_param=calloc(vi->books,sizeof(static_codebook *));*/ + for(i=0;i<vi->books;i++){ + vi->book_param[i]=calloc(1,sizeof(static_codebook)); + if(vorbis_staticbook_unpack(opb,vi->book_param[i]))goto err_out; + } + + /* time backend settings */ + vi->times=_oggpack_read(opb,6)+1; + /*vi->time_type=malloc(vi->times*sizeof(int));*/ + /*vi->time_param=calloc(vi->times,sizeof(void *));*/ + for(i=0;i<vi->times;i++){ + vi->time_type[i]=_oggpack_read(opb,16); + if(vi->time_type[i]<0 || vi->time_type[i]>=VI_TIMEB)goto err_out; + vi->time_param[i]=_time_P[vi->time_type[i]]->unpack(vi,opb); + if(!vi->time_param[i])goto err_out; + } + + /* floor backend settings */ + vi->floors=_oggpack_read(opb,6)+1; + /*vi->floor_type=malloc(vi->floors*sizeof(int));*/ + /*vi->floor_param=calloc(vi->floors,sizeof(void *));*/ + for(i=0;i<vi->floors;i++){ + vi->floor_type[i]=_oggpack_read(opb,16); + if(vi->floor_type[i]<0 || vi->floor_type[i]>=VI_FLOORB)goto err_out; + vi->floor_param[i]=_floor_P[vi->floor_type[i]]->unpack(vi,opb); + if(!vi->floor_param[i])goto err_out; + } + + /* residue backend settings */ + vi->residues=_oggpack_read(opb,6)+1; + /*vi->residue_type=malloc(vi->residues*sizeof(int));*/ + /*vi->residue_param=calloc(vi->residues,sizeof(void *));*/ + for(i=0;i<vi->residues;i++){ + vi->residue_type[i]=_oggpack_read(opb,16); + if(vi->residue_type[i]<0 || vi->residue_type[i]>=VI_RESB)goto err_out; + vi->residue_param[i]=_residue_P[vi->residue_type[i]]->unpack(vi,opb); + if(!vi->residue_param[i])goto err_out; + } + + /* map backend settings */ + vi->maps=_oggpack_read(opb,6)+1; + /*vi->map_type=malloc(vi->maps*sizeof(int));*/ + /*vi->map_param=calloc(vi->maps,sizeof(void *));*/ + for(i=0;i<vi->maps;i++){ + vi->map_type[i]=_oggpack_read(opb,16); + if(vi->map_type[i]<0 || vi->map_type[i]>=VI_MAPB)goto err_out; + vi->map_param[i]=_mapping_P[vi->map_type[i]]->unpack(vi,opb); + if(!vi->map_param[i])goto err_out; + } + + /* mode settings */ + vi->modes=_oggpack_read(opb,6)+1; + /*vi->mode_param=calloc(vi->modes,sizeof(void *));*/ + for(i=0;i<vi->modes;i++){ + vi->mode_param[i]=calloc(1,sizeof(vorbis_info_mode)); + vi->mode_param[i]->blockflag=_oggpack_read(opb,1); + vi->mode_param[i]->windowtype=_oggpack_read(opb,16); + vi->mode_param[i]->transformtype=_oggpack_read(opb,16); + vi->mode_param[i]->mapping=_oggpack_read(opb,8); + + if(vi->mode_param[i]->windowtype>=VI_WINDOWB)goto err_out; + if(vi->mode_param[i]->transformtype>=VI_WINDOWB)goto err_out; + if(vi->mode_param[i]->mapping>=vi->maps)goto err_out; + } + + if(_oggpack_read(opb,1)!=1)goto err_out; /* top level EOP check */ + + return(0); + err_out: + vorbis_info_clear(vi); + return(-1); +} + +/* The Vorbis header is in three packets; the initial small packet in + the first page that identifies basic parameters, a second packet + with bitstream comments and a third packet that holds the + codebook. */ + +int vorbis_synthesis_headerin(vorbis_info *vi,vorbis_comment *vc,ogg_packet *op){ + oggpack_buffer opb; + + if(op){ + _oggpack_readinit(&opb,op->packet,op->bytes); + + /* Which of the three types of header is this? */ + /* Also verify header-ness, vorbis */ + { + char buffer[6]; + int packtype=_oggpack_read(&opb,8); + memset(buffer,0,6); + _v_readstring(&opb,buffer,6); + if(memcmp(buffer,"vorbis",6)){ + /* not a vorbis header */ + return(-1); + } + switch(packtype){ + case 0x01: /* least significant *bit* is read first */ + if(!op->b_o_s){ + /* Not the initial packet */ + return(-1); + } + if(vi->rate!=0){ + /* previously initialized info header */ + return(-1); + } + + return(_vorbis_unpack_info(vi,&opb)); + + case 0x03: /* least significant *bit* is read first */ + if(vi->rate==0){ + /* um... we didn't get the initial header */ + return(-1); + } + + return(_vorbis_unpack_comment(vc,&opb)); + + case 0x05: /* least significant *bit* is read first */ + if(vi->rate==0 || vc->vendor==NULL){ + /* um... we didn;t get the initial header or comments yet */ + return(-1); + } + + return(_vorbis_unpack_books(vi,&opb)); + + default: + /* Not a valid vorbis header type */ + return(-1); + break; + } + } + } + return(-1); +} + +/* pack side **********************************************************/ + +static int _vorbis_pack_info(oggpack_buffer *opb,vorbis_info *vi){ + /* preamble */ + _oggpack_write(opb,0x01,8); + _v_writestring(opb,"vorbis"); + + /* basic information about the stream */ + _oggpack_write(opb,0x00,32); + _oggpack_write(opb,vi->channels,8); + _oggpack_write(opb,vi->rate,32); + + _oggpack_write(opb,vi->bitrate_upper,32); + _oggpack_write(opb,vi->bitrate_nominal,32); + _oggpack_write(opb,vi->bitrate_lower,32); + + _oggpack_write(opb,ilog2(vi->blocksizes[0]),4); + _oggpack_write(opb,ilog2(vi->blocksizes[1]),4); + _oggpack_write(opb,1,1); + + return(0); +} + +static int _vorbis_pack_comment(oggpack_buffer *opb,vorbis_comment *vc){ + char temp[]="Xiphophorus libVorbis I 20000508"; + + /* preamble */ + _oggpack_write(opb,0x03,8); + _v_writestring(opb,"vorbis"); + + /* vendor */ + _oggpack_write(opb,strlen(temp),32); + _v_writestring(opb,temp); + + /* comments */ + + _oggpack_write(opb,vc->comments,32); + if(vc->comments){ + int i; + for(i=0;i<vc->comments;i++){ + if(vc->user_comments[i]){ + _oggpack_write(opb,vc->comment_lengths[i],32); + _v_writestring(opb,vc->user_comments[i]); + }else{ + _oggpack_write(opb,0,32); + } + } + } + _oggpack_write(opb,1,1); + + return(0); +} + +static int _vorbis_pack_books(oggpack_buffer *opb,vorbis_info *vi){ + int i; + _oggpack_write(opb,0x05,8); + _v_writestring(opb,"vorbis"); + + /* books */ + _oggpack_write(opb,vi->books-1,8); + for(i=0;i<vi->books;i++) + if(vorbis_staticbook_pack(vi->book_param[i],opb))goto err_out; + + /* times */ + _oggpack_write(opb,vi->times-1,6); + for(i=0;i<vi->times;i++){ + _oggpack_write(opb,vi->time_type[i],16); + _time_P[vi->time_type[i]]->pack(vi->time_param[i],opb); + } + + /* floors */ + _oggpack_write(opb,vi->floors-1,6); + for(i=0;i<vi->floors;i++){ + _oggpack_write(opb,vi->floor_type[i],16); + _floor_P[vi->floor_type[i]]->pack(vi->floor_param[i],opb); + } + + /* residues */ + _oggpack_write(opb,vi->residues-1,6); + for(i=0;i<vi->residues;i++){ + _oggpack_write(opb,vi->residue_type[i],16); + _residue_P[vi->residue_type[i]]->pack(vi->residue_param[i],opb); + } + + /* maps */ + _oggpack_write(opb,vi->maps-1,6); + for(i=0;i<vi->maps;i++){ + _oggpack_write(opb,vi->map_type[i],16); + _mapping_P[vi->map_type[i]]->pack(vi,vi->map_param[i],opb); + } + + /* modes */ + _oggpack_write(opb,vi->modes-1,6); + for(i=0;i<vi->modes;i++){ + _oggpack_write(opb,vi->mode_param[i]->blockflag,1); + _oggpack_write(opb,vi->mode_param[i]->windowtype,16); + _oggpack_write(opb,vi->mode_param[i]->transformtype,16); + _oggpack_write(opb,vi->mode_param[i]->mapping,8); + } + _oggpack_write(opb,1,1); + + return(0); +err_out: + return(-1); +} + +int vorbis_analysis_headerout(vorbis_dsp_state *v, + vorbis_comment *vc, + ogg_packet *op, + ogg_packet *op_comm, + ogg_packet *op_code){ + vorbis_info *vi=v->vi; + oggpack_buffer opb; + + /* first header packet **********************************************/ + + _oggpack_writeinit(&opb); + if(_vorbis_pack_info(&opb,vi))goto err_out; + + /* build the packet */ + if(v->header)free(v->header); + v->header=malloc(_oggpack_bytes(&opb)); + memcpy(v->header,opb.buffer,_oggpack_bytes(&opb)); + op->packet=v->header; + op->bytes=_oggpack_bytes(&opb); + op->b_o_s=1; + op->e_o_s=0; + op->frameno=0; + + /* second header packet (comments) **********************************/ + + _oggpack_reset(&opb); + if(_vorbis_pack_comment(&opb,vc))goto err_out; + + if(v->header1)free(v->header1); + v->header1=malloc(_oggpack_bytes(&opb)); + memcpy(v->header1,opb.buffer,_oggpack_bytes(&opb)); + op_comm->packet=v->header1; + op_comm->bytes=_oggpack_bytes(&opb); + op_comm->b_o_s=0; + op_comm->e_o_s=0; + op_comm->frameno=0; + + /* third header packet (modes/codebooks) ****************************/ + + _oggpack_reset(&opb); + if(_vorbis_pack_books(&opb,vi))goto err_out; + + if(v->header2)free(v->header2); + v->header2=malloc(_oggpack_bytes(&opb)); + memcpy(v->header2,opb.buffer,_oggpack_bytes(&opb)); + op_code->packet=v->header2; + op_code->bytes=_oggpack_bytes(&opb); + op_code->b_o_s=0; + op_code->e_o_s=0; + op_code->frameno=0; + + _oggpack_writeclear(&opb); + return(0); + err_out: + _oggpack_writeclear(&opb); + memset(op,0,sizeof(ogg_packet)); + memset(op_comm,0,sizeof(ogg_packet)); + memset(op_code,0,sizeof(ogg_packet)); + + if(v->header)free(v->header); + if(v->header1)free(v->header1); + if(v->header2)free(v->header2); + v->header=NULL; + v->header1=NULL; + v->header2=NULL; + return(-1); +} + diff --git a/lib/mapping0.c b/lib/mapping0.c new file mode 100644 index 00000000..989e169b --- /dev/null +++ b/lib/mapping0.c @@ -0,0 +1,383 @@ +/******************************************************************** + * * + * 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: channel mapping 0 implementation + last mod: $Id: mapping0.c,v 1.14.2.1 2000/08/15 08:33:44 xiphmont Exp $ + + ********************************************************************/ + +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include "vorbis/codec.h" +#include "vorbis/backends.h" +#include "bitwise.h" +#include "bookinternal.h" +#include "registry.h" +#include "psy.h" +#include "misc.h" + +/* simplistic, wasteful way of doing this (unique lookup for each + mode/submapping); there should be a central repository for + identical lookups. That will require minor work, so I'm putting it + off as low priority. + + Why a lookup for each backend in a given mode? Because the + blocksize is set by the mode, and low backend lookups may require + parameters from other areas of the mode/mapping */ + +typedef struct { + vorbis_info_mode *mode; + vorbis_info_mapping0 *map; + + vorbis_look_time **time_look; + vorbis_look_floor **floor_look; + + vorbis_look_residue **residue_look; + vorbis_look_psy *psy_look; + + vorbis_func_time **time_func; + vorbis_func_floor **floor_func; + vorbis_func_residue **residue_func; + + int ch; + double **decay; + long lastframe; /* if a different mode is called, we need to + invalidate decay */ +} vorbis_look_mapping0; + +static void free_info(vorbis_info_mapping *i){ + if(i){ + memset(i,0,sizeof(vorbis_info_mapping0)); + free(i); + } +} + +static void free_look(vorbis_look_mapping *look){ + int i; + vorbis_look_mapping0 *l=(vorbis_look_mapping0 *)look; + if(l){ + for(i=0;i<l->map->submaps;i++){ + l->time_func[i]->free_look(l->time_look[i]); + l->floor_func[i]->free_look(l->floor_look[i]); + l->residue_func[i]->free_look(l->residue_look[i]); + if(l->psy_look)_vp_psy_clear(l->psy_look+i); + } + + if(l->decay){ + for(i=0;i<l->ch;i++){ + if(l->decay[i])free(l->decay[i]); + } + free(l->decay); + } + + free(l->time_func); + free(l->floor_func); + free(l->residue_func); + free(l->time_look); + free(l->floor_look); + free(l->residue_look); + if(l->psy_look)free(l->psy_look); + memset(l,0,sizeof(vorbis_look_mapping0)); + free(l); + } +} + +static vorbis_look_mapping *look(vorbis_dsp_state *vd,vorbis_info_mode *vm, + vorbis_info_mapping *m){ + int i; + vorbis_info *vi=vd->vi; + vorbis_look_mapping0 *look=calloc(1,sizeof(vorbis_look_mapping0)); + vorbis_info_mapping0 *info=look->map=(vorbis_info_mapping0 *)m; + look->mode=vm; + + look->time_look=calloc(info->submaps,sizeof(vorbis_look_time *)); + look->floor_look=calloc(info->submaps,sizeof(vorbis_look_floor *)); + + look->residue_look=calloc(info->submaps,sizeof(vorbis_look_residue *)); + if(vi->psys)look->psy_look=calloc(info->submaps,sizeof(vorbis_look_psy)); + + look->time_func=calloc(info->submaps,sizeof(vorbis_func_time *)); + look->floor_func=calloc(info->submaps,sizeof(vorbis_func_floor *)); + look->residue_func=calloc(info->submaps,sizeof(vorbis_func_residue *)); + + for(i=0;i<info->submaps;i++){ + int timenum=info->timesubmap[i]; + int floornum=info->floorsubmap[i]; + int resnum=info->residuesubmap[i]; + + look->time_func[i]=_time_P[vi->time_type[timenum]]; + look->time_look[i]=look->time_func[i]-> + look(vd,vm,vi->time_param[timenum]); + look->floor_func[i]=_floor_P[vi->floor_type[floornum]]; + look->floor_look[i]=look->floor_func[i]-> + look(vd,vm,vi->floor_param[floornum]); + look->residue_func[i]=_residue_P[vi->residue_type[resnum]]; + look->residue_look[i]=look->residue_func[i]-> + look(vd,vm,vi->residue_param[resnum]); + + if(vi->psys && vd->analysisp){ + int psynum=info->psysubmap[i]; + _vp_psy_init(look->psy_look+i,vi->psy_param[psynum], + vi->blocksizes[vm->blockflag]/2,vi->rate); + } + } + + look->ch=vi->channels; + if(vi->psys){ + look->decay=calloc(vi->channels,sizeof(double *)); + for(i=0;i<vi->channels;i++) + look->decay[i]=calloc(vi->blocksizes[vm->blockflag]/2,sizeof(double)); + } + + return(look); +} + +static void pack(vorbis_info *vi,vorbis_info_mapping *vm,oggpack_buffer *opb){ + int i; + vorbis_info_mapping0 *info=(vorbis_info_mapping0 *)vm; + + _oggpack_write(opb,info->submaps-1,4); + /* we don't write the channel submappings if we only have one... */ + if(info->submaps>1){ + for(i=0;i<vi->channels;i++) + _oggpack_write(opb,info->chmuxlist[i],4); + } + for(i=0;i<info->submaps;i++){ + _oggpack_write(opb,info->timesubmap[i],8); + _oggpack_write(opb,info->floorsubmap[i],8); + _oggpack_write(opb,info->residuesubmap[i],8); + } +} + +/* also responsible for range checking */ +static vorbis_info_mapping *unpack(vorbis_info *vi,oggpack_buffer *opb){ + int i; + vorbis_info_mapping0 *info=calloc(1,sizeof(vorbis_info_mapping0)); + memset(info,0,sizeof(vorbis_info_mapping0)); + + info->submaps=_oggpack_read(opb,4)+1; + + if(info->submaps>1){ + for(i=0;i<vi->channels;i++){ + info->chmuxlist[i]=_oggpack_read(opb,4); + if(info->chmuxlist[i]>=info->submaps)goto err_out; + } + } + for(i=0;i<info->submaps;i++){ + info->timesubmap[i]=_oggpack_read(opb,8); + if(info->timesubmap[i]>=vi->times)goto err_out; + info->floorsubmap[i]=_oggpack_read(opb,8); + if(info->floorsubmap[i]>=vi->floors)goto err_out; + info->residuesubmap[i]=_oggpack_read(opb,8); + if(info->residuesubmap[i]>=vi->residues)goto err_out; + } + + return info; + + err_out: + free_info(info); + return(NULL); +} + +#include <stdio.h> +#include "os.h" +#include "lpc.h" +#include "lsp.h" +#include "envelope.h" +#include "mdct.h" +#include "psy.h" +#include "bitwise.h" +#include "spectrum.h" +#include "scales.h" + +/* no time mapping implementation for now */ +static long seq=0; +static int forward(vorbis_block *vb,vorbis_look_mapping *l){ + vorbis_dsp_state *vd=vb->vd; + vorbis_info *vi=vd->vi; + vorbis_look_mapping0 *look=(vorbis_look_mapping0 *)l; + vorbis_info_mapping0 *info=look->map; + vorbis_info_mode *mode=look->mode; + int n=vb->pcmend; + int i,j; + double *window=vd->window[vb->W][vb->lW][vb->nW][mode->windowtype]; + + double **pcmbundle=alloca(sizeof(double *)*vi->channels); + int *nonzero=alloca(sizeof(int)*vi->channels); + + /* time domain pre-window: NONE IMPLEMENTED */ + + /* window the PCM data: takes PCM vector, vb; modifies PCM vector */ + + for(i=0;i<vi->channels;i++){ + double *pcm=vb->pcm[i]; + for(j=0;j<n;j++) + pcm[j]*=window[j]; + } + + /* time-domain post-window: NONE IMPLEMENTED */ + + /* transform the PCM data; takes PCM vector, vb; modifies PCM vector */ + /* only MDCT right now.... */ + for(i=0;i<vi->channels;i++){ + double *pcm=vb->pcm[i]; + mdct_forward(vd->transform[vb->W][0],pcm,pcm); + } + + { + double *floor=_vorbis_block_alloc(vb,n*sizeof(double)/2); + + for(i=0;i<vi->channels;i++){ + double *pcm=vb->pcm[i]; + double *decay=look->decay[i]; + int submap=info->chmuxlist[i]; + + /* if some other mode/mapping was called last frame, our decay + accumulator is out of date. Clear it. */ + if(look->lastframe+1 != vb->sequence) + memset(decay,0,n*sizeof(double)/2); + + /* perform psychoacoustics; do masking */ + _vp_compute_mask(look->psy_look+submap,pcm,floor,decay); + + _analysis_output("decay",seq,decay,n/2,0,1); + _analysis_output("mdct",seq,pcm,n/2,0,1); + _analysis_output("lmdct",seq,pcm,n/2,0,0); + _analysis_output("prefloor",seq,floor,n/2,0,1); + + /* perform floor encoding */ + nonzero[i]=look->floor_func[submap]-> + forward(vb,look->floor_look[submap],floor,floor); + + _analysis_output("floor",seq,floor,n/2,0,1); + + /* apply the floor, do optional noise levelling */ + _vp_apply_floor(look->psy_look+submap,pcm,floor); + + _analysis_output("res",seq++,pcm,n/2,0,0); + +#ifdef TRAIN_RES + if(nonzero[i]){ + FILE *of; + char buffer[80]; + int i; + + sprintf(buffer,"residue_%d.vqd",vb->mode); + of=fopen(buffer,"a"); + for(i=0;i<n/2;i++) + fprintf(of,"%.2f, ",pcm[i]); + fprintf(of,"\n"); + fclose(of); + } +#endif + + } + + /* perform residue encoding with residue mapping; this is + multiplexed. All the channels belonging to one submap are + encoded (values interleaved), then the next submap, etc */ + + for(i=0;i<info->submaps;i++){ + int ch_in_bundle=0; + for(j=0;j<vi->channels;j++){ + if(info->chmuxlist[j]==i && nonzero[j]==1){ + pcmbundle[ch_in_bundle++]=vb->pcm[j]; + } + } + + look->residue_func[i]->forward(vb,look->residue_look[i], + pcmbundle,ch_in_bundle); + } + } + + look->lastframe=vb->sequence; + return(0); +} + +static int inverse(vorbis_block *vb,vorbis_look_mapping *l){ + vorbis_dsp_state *vd=vb->vd; + vorbis_info *vi=vd->vi; + vorbis_look_mapping0 *look=(vorbis_look_mapping0 *)l; + vorbis_info_mapping0 *info=look->map; + vorbis_info_mode *mode=look->mode; + int i,j; + long n=vb->pcmend=vi->blocksizes[vb->W]; + + double *window=vd->window[vb->W][vb->lW][vb->nW][mode->windowtype]; + double **pcmbundle=alloca(sizeof(double *)*vi->channels); + int *nonzero=alloca(sizeof(int)*vi->channels); + + /* time domain information decode (note that applying the + information would have to happen later; we'll probably add a + function entry to the harness for that later */ + /* NOT IMPLEMENTED */ + + /* recover the spectral envelope; store it in the PCM vector for now */ + for(i=0;i<vi->channels;i++){ + double *pcm=vb->pcm[i]; + int submap=info->chmuxlist[i]; + nonzero[i]=look->floor_func[submap]-> + inverse(vb,look->floor_look[submap],pcm); + _analysis_output("ifloor",seq+i,pcm,n/2,0,1); + } + + /* recover the residue, apply directly to the spectral envelope */ + + for(i=0;i<info->submaps;i++){ + int ch_in_bundle=0; + for(j=0;j<vi->channels;j++){ + if(info->chmuxlist[j]==i && nonzero[j]) + pcmbundle[ch_in_bundle++]=vb->pcm[j]; + } + + look->residue_func[i]->inverse(vb,look->residue_look[i],pcmbundle,ch_in_bundle); + } + + /* transform the PCM data; takes PCM vector, vb; modifies PCM vector */ + /* only MDCT right now.... */ + for(i=0;i<vi->channels;i++){ + double *pcm=vb->pcm[i]; + _analysis_output("out",seq+i,pcm,n/2,0,1); + mdct_backward(vd->transform[vb->W][0],pcm,pcm); + } + + /* now apply the decoded pre-window time information */ + /* NOT IMPLEMENTED */ + + /* window the data */ + for(i=0;i<vi->channels;i++){ + double *pcm=vb->pcm[i]; + if(nonzero[i]) + for(j=0;j<n;j++) + pcm[j]*=window[j]; + else + for(j=0;j<n;j++) + pcm[j]=0.; + _analysis_output("final",seq++,pcm,n,0,0); + } + + /* now apply the decoded post-window time information */ + /* NOT IMPLEMENTED */ + + /* all done! */ + return(0); +} + +/* export hooks */ +vorbis_func_mapping mapping0_exportbundle={ + &pack,&unpack,&look,&free_info,&free_look,&forward,&inverse +}; + + + @@ -12,7 +12,7 @@ ******************************************************************** function: psychoacoustics not including preecho - last mod: $Id: psy.c,v 1.23.4.9 2000/08/09 20:22:04 xiphmont Exp $ + last mod: $Id: psy.c,v 1.23.4.10 2000/08/15 08:33:44 xiphmont Exp $ ********************************************************************/ @@ -303,28 +303,6 @@ void _vp_psy_clear(vorbis_look_psy *p){ } } -static void compute_decay_adaptive(vorbis_look_psy *p,double *f, double *decay, int n){ - /* handle decay */ - int i; - double decscale=1.-pow(p->vi->decay_coeff,n); - double attscale=1.-pow(p->vi->attack_coeff,n); - static int frameno=0; - - _analysis_output("drive",frameno,f,n,0,1); - _analysis_output("decay",frameno++,decay,n,0,1); - - for(i=0;i<n;i++){ - double del=f[i]-decay[i]; - if(del>0) - /* add energy */ - decay[i]+=del*attscale; - else - /* remove energy */ - decay[i]+=del*decscale; - if(decay[i]>f[i])f[i]=decay[i]; - } -} - static void compute_decay_fixed(vorbis_look_psy *p,double *f, double *decay, int n){ /* handle decay */ int i; @@ -333,9 +311,6 @@ static void compute_decay_fixed(vorbis_look_psy *p,double *f, double *decay, int static int frameno=0; - _analysis_output("drive",frameno,f,n,0,1); - _analysis_output("decay",frameno++,decay,n,0,1); - for(i=0;i<n;i++){ double pre=decay[i]; if(decay[i]){ @@ -543,7 +518,7 @@ static void bark_noise(long n,double *b,double *f,double *noise){ memset(norm,0,n*sizeof(double)); while(hi<n){ - val=todB(f[i]*f[i])+200.; + val=todB(f[i]*f[i])+400.; del=1./(i-lo); noise[lo]+=val*del; noise[i]-=val*del; @@ -558,8 +533,8 @@ static void bark_noise(long n,double *b,double *f,double *noise){ i++; - for(;b[hi]-.5<b[i] && hi<n;hi++); - for(;b[lo]+.5<b[i] && lo<i;lo++); + for(;hi<n && b[hi]-.3<b[i];hi++); + for(;lo<i-1 && b[lo]+.3<b[i];lo++); if(i==hi)hi++; } @@ -569,8 +544,7 @@ static void bark_noise(long n,double *b,double *f,double *noise){ long hilo=hi-lo; for(;i<n;i++){ - val=todB(f[i]*f[i])+200.; - + val=todB(f[i]*f[i])+400.; del=1./(hii); noise[i]-=val*del; norm[i]-=del; @@ -582,7 +556,7 @@ static void bark_noise(long n,double *b,double *f,double *noise){ norm[i]-=del; } for(i=1,lo=n-ilo;lo<n;lo++,i++){ - val=todB(f[n-i]*f[n-i])+200.; + val=todB(f[n-i]*f[n-i])+400.; del=1./ilo; noise[lo]+=val*del; norm[lo]+=del; @@ -605,10 +579,13 @@ static void bark_noise(long n,double *b,double *f,double *noise){ for(i=0;i<n;i++){ val+=norm[i]; acc+=noise[i]; - if(val==0) + if(val==0){ noise[i]=0.; - else - noise[i]=sqrt(fromdB(acc/val-200.)); + norm[i]=0; + }else{ + double v=acc/val-400; + noise[i]=sqrt(fromdB(v)); + } } } @@ -625,23 +602,24 @@ void _vp_compute_mask(vorbis_look_psy *p,double *f, double *flr, double *decay){ double *smooth=alloca(sizeof(double)*p->n); - double *seed=alloca(sizeof(double)*p->n); int i,n=p->n; double specmax=0.; + double *seed=alloca(sizeof(double)*p->n); + double *seed2=alloca(sizeof(double)*p->n); + memset(flr,0,n*sizeof(double)); - memset(seed,0,n*sizeof(double)); /* noise masking */ if(p->vi->noisemaskp){ - /* don't use the smoothed data for noise */ - bark_noise(n,p->bark,f,smooth); - /*_analysis_output("noise",frameno,work2,n,0,1);*/ - seed_point(p,p->noiseatt,smooth,flr,specmax); + memset(seed,0,n*sizeof(double)); + bark_noise(n,p->bark,f,seed); + seed_point(p,p->noiseatt,seed,flr,specmax); + } + /* smooth the data is that's called for ********************************/ for(i=0;i<n;i++)smooth[i]=fabs(f[i]); - if(p->vi->smoothp){ /* compute power^.5 of three neighboring bins to smooth for peaks that get split twixt bins/peaks that nail the bin. This evens @@ -660,7 +638,7 @@ void _vp_compute_mask(vorbis_look_psy *p,double *f, smooth[n-1]=sqrt(acc); } - /* find the highest peak so we know the limits */ + /* find the highest peak so we know the limits *************************/ for(i=0;i<n;i++){ if(smooth[i]>specmax)specmax=smooth[i]; } @@ -678,52 +656,32 @@ void _vp_compute_mask(vorbis_look_psy *p,double *f, } } - /* peak attenuation */ + /* peak attenuation ******/ if(p->vi->peakattp){ memset(seed,0,n*sizeof(double)); seed_att(p,p->peakatt,smooth,seed,specmax); max_seeds(p,seed,flr); } - /* seed the tone masking */ + /* tone masking */ if(p->vi->tonemaskp){ memset(seed,0,n*sizeof(double)); + memset(seed2,0,n*sizeof(double)); - seed_generic(p,p->tonecurves,smooth,flr,seed,specmax); - - /* chase the seeds */ - max_seeds(p,seed,seed); + seed_generic(p,p->tonecurves,smooth,flr,seed2,specmax); + max_seeds(p,seed2,seed2); + + for(i=0;i<n;i++)if(seed2[i]<flr[i])seed2[i]=flr[i]; + for(i=0;i<n;i++)if(seed2[i]<decay[i])seed2[i]=decay[i]; + seed_generic(p,p->tonecurves,smooth,seed2,seed,specmax); + max_seeds(p,seed,seed); + if(p->vi->decayp) compute_decay_fixed(p,seed,decay,n); - + for(i=0;i<n;i++)if(flr[i]<seed[i])flr[i]=seed[i]; - - } - - /* noise culling */ - if(p->vi->noisecullp){ - int j; - - /* march down from Nyquist to the first peak that exceeds the - masking threshhold */ - for(i=n-1;i>0;i--){ - if(flr[i]<fabs(f[i])){ - break; - }else{ - f[i]=0; - } - } - - /* take the edge off the steep ATH rise in treble */ - /*for(j=i+1;j<n;j++) - flr[j]=flr[j-1];*/ - - /* Smooth the falloff a bit */ - - /* cull out addiitonal bands */ - - + } frameno++; diff --git a/lib/psytune.c b/lib/psytune.c index 2b5120bb..5191db0a 100644 --- a/lib/psytune.c +++ b/lib/psytune.c @@ -13,7 +13,7 @@ function: simple utility that runs audio through the psychoacoustics without encoding - last mod: $Id: psytune.c,v 1.4.4.2 2000/07/31 19:41:00 xiphmont Exp $ + last mod: $Id: psytune.c,v 1.4.4.3 2000/08/15 08:33:45 xiphmont Exp $ ********************************************************************/ @@ -34,7 +34,7 @@ static vorbis_info_psy _psy_set0={ 1,/*athp*/ 1,/*decayp*/ 1,/*smoothp*/ - 1,.2, + 0,.2, -100., -140., @@ -64,28 +64,27 @@ static vorbis_info_psy _psy_set0={ {-40.,-40.,-40.,-40.,-40.,-50.,-60.,-70.,-80.,-90.,-100.}, /*16000*/ }, - 0,/* peakattp */ - /* 0 10 20 30 40 50 60 70 80 90 100 */ + 1,/* peakattp */ {{-14.,-16.,-18.,-19.,-20.,-21.,-22.,-22.,-22.,-22.,-22.}, /*63*/ {-14.,-16.,-18.,-19.,-20.,-21.,-22.,-22.,-22.,-22.,-22.}, /*88*/ {-14.,-16.,-18.,-19.,-20.,-21.,-22.,-22.,-22.,-22.,-22.}, /*125*/ - {-14.,-16.,-18.,-19.,-20.,-21.,-22.,-22.,-22.,-22.,-22.}, /*175*/ - {-14.,-16.,-18.,-19.,-20.,-21.,-22.,-22.,-22.,-22.,-22.}, /*250*/ - {-14.,-16.,-18.,-19.,-20.,-20.,-20.,-21.,-22.,-22.,-22.}, /*350*/ - {-14.,-16.,-18.,-18.,-18.,-19.,-20.,-20.,-20.,-22.,-22.}, /*500*/ - {-14.,-16.,-18.,-18.,-18.,-18.,-18.,-19.,-20.,-22.,-22.}, /*700*/ - {-14.,-16.,-16.,-17.,-18.,-18.,-18.,-19.,-20.,-22.,-22.}, /*1000*/ - {-14.,-16.,-16.,-17.,-18.,-18.,-18.,-19.,-20.,-22.,-22.}, /*1400*/ - {-14.,-15.,-18.,-18.,-18.,-18.,-18.,-19.,-20.,-22.,-22.}, /*2000*/ - {-14.,-14.,-18.,-18.,-18.,-18.,-18.,-19.,-20.,-22.,-22.}, /*2800*/ - {-12.,-13.,-16.,-17.,-17.,-17.,-18.,-19.,-20.,-22.,-22.}, /*4000*/ - {-10.,-12.,-14.,-15.,-16.,-16.,-17.,-18.,-20.,-22.,-22.}, /*5600*/ - { -8.,-11.,-12.,-13.,-14.,-15.,-16.,-17.,-18.,-22.,-22.}, /*8000*/ - { -8.,-10.,-12.,-13.,-14.,-15.,-16.,-16.,-16.,-22.,-22.}, /*11500*/ - { -8.,-10.,-12.,-13.,-14.,-15.,-16.,-16.,-16.,-22.,-22.}, /*16000*/ + { -7., -8., -9.,-10.,-10.,-11.,-12.,-13.,-15.,-18.,-20.}, /*8000*/ + { -7., -8., -9.,-10.,-10.,-11.,-12.,-13.,-15.,-18.,-20.}, /*8000*/ + { -7., -8., -9.,-10.,-10.,-11.,-12.,-13.,-15.,-18.,-20.}, /*8000*/ + { -7., -8., -9.,-10.,-10.,-11.,-12.,-13.,-15.,-18.,-20.}, /*8000*/ + { -7., -8., -9.,-10.,-10.,-11.,-12.,-13.,-15.,-18.,-20.}, /*8000*/ + { -7., -8., -9.,-10.,-10.,-11.,-12.,-13.,-15.,-18.,-20.}, /*8000*/ + { -7., -8., -9.,-10.,-10.,-11.,-12.,-13.,-15.,-18.,-20.}, /*8000*/ + { -7., -8., -9.,-10.,-10.,-11.,-12.,-13.,-15.,-18.,-20.}, /*8000*/ + { -7., -8., -9.,-10.,-10.,-11.,-12.,-13.,-15.,-18.,-20.}, /*8000*/ + { -7., -8., -9.,-10.,-10.,-11.,-12.,-13.,-15.,-18.,-20.}, /*8000*/ + { -7., -8., -9.,-10.,-10.,-11.,-12.,-13.,-15.,-18.,-20.}, /*8000*/ + { -7., -8., -9.,-10.,-10.,-11.,-12.,-13.,-15.,-16.,-17.}, /*8000*/ + { -6., -7., -9., -9., -9., -9.,-10.,-11.,-12.,-13.,-14.}, /*11500*/ + { -6., -6., -9., -9., -9., -9., -9., -9.,-10.,-11.,-12.}, /*16000*/ }, - 0,/*noisemaskp */ + 1,/*noisemaskp */ /* 0 10 20 30 40 50 60 70 80 90 100 */ {{-99.,-99.,-99.,-99.,-99.,-99.,-99.,-199.,-199.,-199.,-199.}, /*63*/ {-99.,-99.,-99.,-99.,-99.,-99.,-99.,-199.,-199.,-199.,-199.}, /*88*/ @@ -93,25 +92,25 @@ static vorbis_info_psy _psy_set0={ {-99.,-99.,-99.,-99.,-99.,-99.,-99.,-199.,-199.,-199.,-199.}, /*175*/ {-99.,-99.,-99.,-99.,-99.,-99.,-99.,-199.,-199.,-199.,-199.}, /*250*/ {-99.,-99.,-99.,-99.,-99.,-99.,-99.,-199.,-199.,-199.,-199.}, /*350*/ - {-99.,-99.,-99.,-99.,-99.,-99.,-99.,-199.,-199.,-199.,-199.}, /*500*/ - {-99.,-99.,-99.,-99.,-99.,-99.,-99.,-199.,-199.,-199.,-199.}, /*700*/ - {-99.,-99.,-99.,-99.,-99.,-99.,-99.,-199.,-199.,-199.,-199.}, /*100*/ - {-99.,-99.,-99.,-99.,-99.,-99.,-99.,-199.,-199.,-199.,-199.}, /*1400/ - { -0., -0., -0., -0., -0., -0., -0., -0., -0., -0., -0.}, /*2000*/ - { -0., -0., -0., -0., -0., -0., -0., -0., -0., -0., -0.}, /*2800*/ + {-99.,-99.,-99.,-99.,-99.,-99.,-99.,-199.,-199.,-199.,-199.}, /*350*/ + {-99.,-99.,-99.,-99.,-99.,-99.,-99.,-199.,-199.,-199.,-199.}, /*350*/ + { -6., -6., -6., -6., -6., -6., -6., -6., -6., -6., -6.}, /*2000*/ + { -6., -6., -6., -6., -6., -6., -6., -6., -6., -6., -6.}, /*2000*/ + { -6., -6., -6., -6., -6., -6., -6., -6., -6., -6., -6.}, /*2000*/ + { -6., -6., -6., -6., -6., -6., -6., -6., -6., -6., -6.}, /*2800*/ { -0., -0., -0., -0., -0., -0., -0., -0., -0., -0., -0.}, /*4000*/ - { -0., -0., -0., -0., -0., -0., -0., -0., -0., -0., -0.}, /*5600*/ - { -0., -0., -0., -0., -0., -0., -0., -0., -0., -0., -0.}, /*8000*/ - { -0., -0., -0., -0., -0., -0., -0., -0., -0., -0., -0.}, /*11500*/ - { -0., -0., -0., -0., -0., -0., -0., -0., -0., -0., -0.}, /*16000*/ + { 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.}, /*5600*/ + { 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4.}, /*8000*/ + { 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4.}, /*11500*/ + { 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4.}, /*16000*/ }, 110., - .9998, .9998, /* attack/decay control */ + -0, -.005, /* attack/decay control */ }; -static int noisy=1; +static int noisy=0; void analysis(char *base,int i,double *v,int n,int bark,int dB){ if(noisy){ int j; @@ -305,6 +304,9 @@ int main(int argc,char *argv[]){ analysis("floor",frameno,floor,framesize/2,1,1); _vp_apply_floor(&p_look,pcm[i],floor); + /*r(j=0;j<framesize/2;j++) + if(fabs(pcm[i][j])<1.)pcm[i][j]=0;*/ + analysis("quant",frameno,pcm[i],framesize/2,1,1); /* re-add floor */ diff --git a/lib/res0.c b/lib/res0.c new file mode 100644 index 00000000..1f07a36e --- /dev/null +++ b/lib/res0.c @@ -0,0 +1,384 @@ +/******************************************************************** + * * + * 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: residue backend 0 implementation + last mod: $Id: res0.c,v 1.15.4.1 2000/08/15 08:33:46 xiphmont Exp $ + + ********************************************************************/ + +/* Slow, slow, slow, simpleminded and did I mention it was slow? The + encode/decode loops are coded for clarity and performance is not + yet even a nagging little idea lurking in the shadows. Oh and BTW, + it's slow. */ + +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include <stdio.h> +#include "vorbis/codec.h" +#include "bitwise.h" +#include "registry.h" +#include "bookinternal.h" +#include "sharedbook.h" +#include "misc.h" +#include "os.h" + +typedef struct { + vorbis_info_residue0 *info; + int map; + + int parts; + codebook *phrasebook; + codebook ***partbooks; + + int partvals; + int **decodemap; +} vorbis_look_residue0; + +void free_info(vorbis_info_residue *i){ + if(i){ + memset(i,0,sizeof(vorbis_info_residue0)); + free(i); + } +} + +void free_look(vorbis_look_residue *i){ + int j; + if(i){ + vorbis_look_residue0 *look=(vorbis_look_residue0 *)i; + for(j=0;j<look->parts;j++) + if(look->partbooks[j])free(look->partbooks[j]); + free(look->partbooks); + for(j=0;j<look->partvals;j++) + free(look->decodemap[j]); + free(look->decodemap); + memset(i,0,sizeof(vorbis_look_residue0)); + free(i); + } +} + +void pack(vorbis_info_residue *vr,oggpack_buffer *opb){ + vorbis_info_residue0 *info=(vorbis_info_residue0 *)vr; + int j,acc=0; + _oggpack_write(opb,info->begin,24); + _oggpack_write(opb,info->end,24); + + _oggpack_write(opb,info->grouping-1,24); /* residue vectors to group and + code with a partitioned book */ + _oggpack_write(opb,info->partitions-1,6); /* possible partition choices */ + _oggpack_write(opb,info->groupbook,8); /* group huffman book */ + for(j=0;j<info->partitions;j++){ + _oggpack_write(opb,info->secondstages[j],4); /* zero *is* a valid choice */ + acc+=info->secondstages[j]; + } + for(j=0;j<acc;j++) + _oggpack_write(opb,info->booklist[j],8); + +} + +/* vorbis_info is for range checking */ +vorbis_info_residue *unpack(vorbis_info *vi,oggpack_buffer *opb){ + int j,acc=0; + vorbis_info_residue0 *info=calloc(1,sizeof(vorbis_info_residue0)); + + info->begin=_oggpack_read(opb,24); + info->end=_oggpack_read(opb,24); + info->grouping=_oggpack_read(opb,24)+1; + info->partitions=_oggpack_read(opb,6)+1; + info->groupbook=_oggpack_read(opb,8); + for(j=0;j<info->partitions;j++){ + acc+=info->secondstages[j]=_oggpack_read(opb,4); + } + for(j=0;j<acc;j++) + info->booklist[j]=_oggpack_read(opb,8); + + if(info->groupbook>=vi->books)goto errout; + for(j=0;j<acc;j++) + if(info->booklist[j]>=vi->books)goto errout; + + return(info); + errout: + free_info(info); + return(NULL); +} + +vorbis_look_residue *look (vorbis_dsp_state *vd,vorbis_info_mode *vm, + vorbis_info_residue *vr){ + vorbis_info_residue0 *info=(vorbis_info_residue0 *)vr; + vorbis_look_residue0 *look=calloc(1,sizeof(vorbis_look_residue0)); + int j,k,acc=0; + int dim; + look->info=info; + look->map=vm->mapping; + + look->parts=info->partitions; + look->phrasebook=vd->fullbooks+info->groupbook; + dim=look->phrasebook->dim; + + look->partbooks=calloc(look->parts,sizeof(codebook **)); + + for(j=0;j<look->parts;j++){ + int stages=info->secondstages[j]; + if(stages){ + look->partbooks[j]=malloc(stages*sizeof(codebook *)); + for(k=0;k<stages;k++) + look->partbooks[j][k]=vd->fullbooks+info->booklist[acc++]; + } + } + + look->partvals=rint(pow(look->parts,dim)); + look->decodemap=malloc(look->partvals*sizeof(int *)); + for(j=0;j<look->partvals;j++){ + long val=j; + long mult=look->partvals/look->parts; + look->decodemap[j]=malloc(dim*sizeof(int)); + for(k=0;k<dim;k++){ + long deco=val/mult; + val-=deco*mult; + mult/=look->parts; + look->decodemap[j][k]=deco; + } + } + + return(look); +} + + +/* does not guard against invalid settings; eg, a subn of 16 and a + subgroup request of 32. Max subn of 128 */ +static int _testhack(double *vec,int n,vorbis_look_residue0 *look, + int auxparts,int auxpartnum){ + vorbis_info_residue0 *info=look->info; + int i,j=0; + double max,localmax=0.; + double temp[128]; + double entropy[8]; + + /* setup */ + for(i=0;i<n;i++)temp[i]=fabs(vec[i]); + + /* handle case subgrp==1 outside */ + for(i=0;i<n;i++) + if(temp[i]>localmax)localmax=temp[i]; + max=localmax; + + for(i=0;i<n;i++)temp[i]=rint(temp[i]); + + while(1){ + entropy[j]=localmax; + n>>=1; + j++; + + if(n<=0)break; + for(i=0;i<n;i++){ + temp[i]+=temp[i+n]; + } + localmax=0.; + for(i=0;i<n;i++) + if(temp[i]>localmax)localmax=temp[i]; + } + + for(i=0;i<auxparts-1;i++) + if(auxpartnum<info->blimit[i] && + entropy[info->subgrp[i]]<=info->entmax[i] && + max<=info->ampmax[i]) + break; + + return(i); +} + +static int _encodepart(oggpack_buffer *opb,double *vec, int n, + int stages, codebook **books,int mode,int part){ + int i,j,bits=0; + + for(j=0;j<stages;j++){ + int dim=books[j]->dim; + int step=n/dim; + for(i=0;i<step;i++){ + int entry=vorbis_book_besterror(books[j],vec+i,step,0); +#ifdef TRAIN_RESENT + { + char buf[80]; + FILE *f; + sprintf(buf,"res0_%da%d_%d.vqd",mode,j,part); + f=fopen(buf,"a"); + fprintf(f,"%d\n",entry); + fclose(f); + } +#endif + bits+=vorbis_book_encode(books[j],entry,opb); + } + } + + return(bits); +} + +static int _decodepart(oggpack_buffer *opb,double *work,double *vec, int n, + int stages, codebook **books){ + int i,j; + + memset(work,0,sizeof(double)*n); + for(j=0;j<stages;j++){ + int dim=books[j]->dim; + int step=n/dim; + for(i=0;i<step;i++) + if(vorbis_book_decodevs(books[j],work+i,opb,step,0)==-1) + return(-1); + } + + for(i=0;i<n;i++) + vec[i]*=work[i]; + + return(0); +} + +int forward(vorbis_block *vb,vorbis_look_residue *vl, + double **in,int ch){ + long i,j,k,l; + vorbis_look_residue0 *look=(vorbis_look_residue0 *)vl; + vorbis_info_residue0 *info=look->info; + + /* move all this setup out later */ + int samples_per_partition=info->grouping; + int possible_partitions=info->partitions; + int partitions_per_word=look->phrasebook->dim; + int n=info->end-info->begin; + long phrasebits=0,resbitsT=0; + long *resbits=alloca(sizeof(long)*possible_partitions); + long *resvals=alloca(sizeof(long)*possible_partitions); + + int partvals=n/samples_per_partition; + int partwords=(partvals+partitions_per_word-1)/partitions_per_word; + long **partword=_vorbis_block_alloc(vb,ch*sizeof(long *)); + + partvals=partwords*partitions_per_word; + + /* we find the patition type for each partition of each + channel. We'll go back and do the interleaved encoding in a + bit. For now, clarity */ + + memset(resbits,0,sizeof(long)*possible_partitions); + memset(resvals,0,sizeof(long)*possible_partitions); + + for(i=0;i<ch;i++){ + partword[i]=_vorbis_block_alloc(vb,n/samples_per_partition*sizeof(long)); + memset(partword[i],0,n/samples_per_partition*sizeof(long)); + } + + for(i=info->begin,l=0;i<info->end;i+=samples_per_partition,l++){ + for(j=0;j<ch;j++) + /* do the partition decision based on the number of 'bits' + needed to encode the block */ + partword[j][l]= + _testhack(in[j]+i,samples_per_partition,look,possible_partitions,l); + + } + /* we code the partition words for each channel, then the residual + words for a partition per channel until we've written all the + residual words for that partition word. Then write the next + parition channel words... */ + + for(i=info->begin,l=0;i<info->end;){ + /* first we encode a partition codeword for each channel */ + for(j=0;j<ch;j++){ + long val=partword[j][l]; + for(k=1;k<partitions_per_word;k++) + val= val*possible_partitions+partword[j][l+k]; + phrasebits+=vorbis_book_encode(look->phrasebook,val,&vb->opb); + } + /* now we encode interleaved residual values for the partitions */ + for(k=0;k<partitions_per_word;k++,l++,i+=samples_per_partition) + for(j=0;j<ch;j++){ + resbits[partword[j][l]]+= + _encodepart(&vb->opb,in[j]+i,samples_per_partition, + info->secondstages[partword[j][l]], + look->partbooks[partword[j][l]],look->map,partword[j][l]); + resvals[partword[j][l]]+=samples_per_partition; + } + + } + + for(i=0;i<possible_partitions;i++)resbitsT+=resbits[i]; + /*fprintf(stderr, + "Encoded %ld res vectors in %ld phrasing and %ld res bits\n\t", + ch*(info->end-info->begin),phrasebits,resbitsT); + for(i=0;i<possible_partitions;i++) + fprintf(stderr,"%ld(%ld):%ld ",i,resvals[i],resbits[i]); + fprintf(stderr,"\n");*/ + + return(0); +} + +/* a truncated packet here just means 'stop working'; it's not an error */ +int inverse(vorbis_block *vb,vorbis_look_residue *vl,double **in,int ch){ + long i,j,k,l,transend=vb->pcmend/2; + vorbis_look_residue0 *look=(vorbis_look_residue0 *)vl; + vorbis_info_residue0 *info=look->info; + + /* move all this setup out later */ + int samples_per_partition=info->grouping; + int partitions_per_word=look->phrasebook->dim; + int n=info->end-info->begin; + + int partvals=n/samples_per_partition; + int partwords=(partvals+partitions_per_word-1)/partitions_per_word; + int **partword=alloca(ch*sizeof(long *)); + double *work=alloca(sizeof(double)*samples_per_partition); + partvals=partwords*partitions_per_word; + + /* make sure we're zeroed up to the start */ + for(j=0;j<ch;j++) + memset(in[j],0,sizeof(double)*info->begin); + + for(i=info->begin,l=0;i<info->end;){ + /* fetch the partition word for each channel */ + for(j=0;j<ch;j++){ + int temp=vorbis_book_decode(look->phrasebook,&vb->opb); + if(temp==-1)goto eopbreak; + partword[j]=look->decodemap[temp]; + if(partword[j]==NULL)goto errout; + } + + /* now we decode interleaved residual values for the partitions */ + for(k=0;k<partitions_per_word;k++,l++,i+=samples_per_partition) + for(j=0;j<ch;j++){ + int part=partword[j][k]; + if(_decodepart(&vb->opb,work,in[j]+i,samples_per_partition, + info->secondstages[part], + look->partbooks[part])==-1)goto eopbreak; + } + } + + eopbreak: + if(i<transend){ + for(j=0;j<ch;j++) + memset(in[j]+i,0,sizeof(double)*(transend-i)); + } + + return(0); + + errout: + for(j=0;j<ch;j++) + memset(in[j],0,sizeof(double)*transend); + return(0); +} + +vorbis_func_residue residue0_exportbundle={ + &pack, + &unpack, + &look, + &free_info, + &free_look, + &forward, + &inverse +}; 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; + } + } +} + + + + |
