diff options
Diffstat (limited to 'apps/codecs/libtremor/framing.c')
-rw-r--r-- | apps/codecs/libtremor/framing.c | 2672 |
1 files changed, 1851 insertions, 821 deletions
diff --git a/apps/codecs/libtremor/framing.c b/apps/codecs/libtremor/framing.c index ac95831d5d..a40e8dec65 100644 --- a/apps/codecs/libtremor/framing.c +++ b/apps/codecs/libtremor/framing.c @@ -1,17 +1,18 @@ /******************************************************************** * * - * THIS FILE IS PART OF THE OggVorbis 'TREMOR' CODEC SOURCE CODE. * - * * + * THIS FILE IS PART OF THE Ogg CONTAINER SOURCE CODE. * * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * * * - * THE OggVorbis 'TREMOR' SOURCE CODE IS (C) COPYRIGHT 1994-2003 * - * BY THE Xiph.Org FOUNDATION http://www.xiph.org/ * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2010 * + * by the Xiph.Org Foundation http://www.xiph.org/ * * * ******************************************************************** - function: decode Ogg streams back into raw packets + function: code raw packets into framed OggSquish stream and + decode Ogg streams back into raw packets + last mod: $Id$ note: The CRC code is directly derived from public domain code by Ross Williams (ross@guest.adelaide.edu.au). See docs/framing.html @@ -19,458 +20,102 @@ ********************************************************************/ -#include "config-tremor.h" +#include <stdlib.h> #include <string.h> #include "ogg.h" -#include "misc.h" - /* A complete description of Ogg framing exists in docs/framing.html */ -/* basic, centralized Ogg memory management based on linked lists of - references to refcounted memory buffers. References and buffers - are both recycled. Buffers are passed around and consumed in - reference form. */ - -static ogg_buffer_state *ogg_buffer_create(void){ - ogg_buffer_state *bs=_ogg_calloc(1,sizeof(*bs)); - return bs; -} - -/* destruction is 'lazy'; there may be memory references outstanding, - and yanking the buffer state out from underneath would be - antisocial. Dealloc what is currently unused and have - _release_one watch for the stragglers to come in. When they do, - finish destruction. */ - -/* call the helper while holding lock */ -static void _ogg_buffer_destroy(ogg_buffer_state *bs){ - ogg_buffer *bt; - ogg_reference *rt; - - if(bs->shutdown){ - - bt=bs->unused_buffers; - rt=bs->unused_references; - - while(bt){ - ogg_buffer *b=bt; - bt=b->ptr.next; - if(b->data)_ogg_free(b->data); - _ogg_free(b); - } - bs->unused_buffers=0; - while(rt){ - ogg_reference *r=rt; - rt=r->next; - _ogg_free(r); - } - bs->unused_references=0; - - if(!bs->outstanding) - _ogg_free(bs); - - } -} - -static void ogg_buffer_destroy(ogg_buffer_state *bs){ - bs->shutdown=1; - _ogg_buffer_destroy(bs); -} - -static ogg_buffer *_fetch_buffer(ogg_buffer_state *bs,long bytes){ - ogg_buffer *ob; - bs->outstanding++; - - /* do we have an unused buffer sitting in the pool? */ - if(bs->unused_buffers){ - ob=bs->unused_buffers; - bs->unused_buffers=ob->ptr.next; - - /* if the unused buffer is too small, grow it */ - if(ob->size<bytes){ - ob->data=_ogg_realloc(ob->data,bytes); - ob->size=bytes; - } - }else{ - /* allocate a new buffer */ - ob=_ogg_malloc(sizeof(*ob)); - ob->data=_ogg_malloc(bytes<16?16:bytes); - ob->size=bytes; - } - - ob->refcount=1; - ob->ptr.owner=bs; - return ob; -} - -STATICIRAM_NOT_MDCT ogg_reference *_fetch_ref(ogg_buffer_state *bs) - ICODE_ATTR_TREMOR_NOT_MDCT; -STATICIRAM_NOT_MDCT ogg_reference *_fetch_ref(ogg_buffer_state *bs){ - ogg_reference *or; - bs->outstanding++; - - /* do we have an unused reference sitting in the pool? */ - if(bs->unused_references){ - or=bs->unused_references; - bs->unused_references=or->next; - }else{ - /* allocate a new reference */ - or=_ogg_malloc(sizeof(*or)); - } - - or->begin=0; - or->length=0; - or->next=0; - return or; -} - -/* fetch a reference pointing to a fresh, initially continguous buffer - of at least [bytes] length */ -static ogg_reference *ogg_buffer_alloc(ogg_buffer_state *bs,long bytes){ - ogg_buffer *ob=_fetch_buffer(bs,bytes); - ogg_reference *or=_fetch_ref(bs); - or->buffer=ob; - return or; -} - -/* enlarge the data buffer in the current link */ -static void ogg_buffer_realloc(ogg_reference *or,long bytes){ - ogg_buffer *ob=or->buffer; - - /* if the unused buffer is too small, grow it */ - if(ob->size<bytes){ - ob->data=_ogg_realloc(ob->data,bytes); - ob->size=bytes; - } +int ogg_page_version(const ogg_page *og){ + return((int)(og->header[4])); } -static void _ogg_buffer_mark_one(ogg_reference *or){ - or->buffer->refcount++; +int ogg_page_continued(const ogg_page *og){ + return((int)(og->header[5]&0x01)); } -/* increase the refcount of the buffers to which the reference points */ -static void ogg_buffer_mark(ogg_reference *or){ - while(or){ - _ogg_buffer_mark_one(or); - or=or->next; - } +int ogg_page_bos(const ogg_page *og){ + return((int)(og->header[5]&0x02)); } -/* duplicate a reference (pointing to the same actual buffer memory) - and increment buffer refcount. If the desired segment begins out - of range, NULL is returned; if the desired segment is simply zero - length, a zero length ref is returned. Partial range overlap - returns the overlap of the ranges */ -static ogg_reference *ogg_buffer_sub(ogg_reference *or,long begin,long length){ - ogg_reference *ret=0,*head=0; - - /* walk past any preceeding fragments we don't want */ - while(or && begin>=or->length){ - begin-=or->length; - or=or->next; - } - - /* duplicate the reference chain; increment refcounts */ - while(or && length){ - ogg_reference *temp=_fetch_ref(or->buffer->ptr.owner); - if(head) - head->next=temp; - else - ret=temp; - head=temp; - head->buffer=or->buffer; - head->begin=or->begin+begin; - head->length=length; - if(head->length>or->length-begin) - head->length=or->length-begin; - - begin=0; - length-=head->length; - or=or->next; - } - - ogg_buffer_mark(ret); - return ret; +int ogg_page_eos(const ogg_page *og){ + return((int)(og->header[5]&0x04)); } -static ogg_reference *ogg_buffer_dup(ogg_reference *or){ - ogg_reference *ret=0,*head=0; - /* duplicate the reference chain; increment refcounts */ - while(or){ - ogg_reference *temp=_fetch_ref(or->buffer->ptr.owner); - if(head) - head->next=temp; - else - ret=temp; - head=temp; - head->buffer=or->buffer; - head->begin=or->begin; - head->length=or->length; - or=or->next; - } - - ogg_buffer_mark(ret); - return ret; +ogg_int64_t ogg_page_granulepos(const ogg_page *og){ + unsigned char *page=og->header; + ogg_int64_t granulepos=page[13]&(0xff); + granulepos= (granulepos<<8)|(page[12]&0xff); + granulepos= (granulepos<<8)|(page[11]&0xff); + granulepos= (granulepos<<8)|(page[10]&0xff); + granulepos= (granulepos<<8)|(page[9]&0xff); + granulepos= (granulepos<<8)|(page[8]&0xff); + granulepos= (granulepos<<8)|(page[7]&0xff); + granulepos= (granulepos<<8)|(page[6]&0xff); + return(granulepos); } -/* split a reference into two references; 'return' is a reference to - the buffer preceeding pos and 'head'/'tail' are the buffer past the - split. If pos is at or past the end of the passed in segment, - 'head/tail' are NULL */ -static ogg_reference *ogg_buffer_split(ogg_reference **tail, - ogg_reference **head,long pos){ - - /* walk past any preceeding fragments to one of: - a) the exact boundary that seps two fragments - b) the fragment that needs split somewhere in the middle */ - ogg_reference *ret=*tail; - ogg_reference *or=*tail; - - while(or && pos>or->length){ - pos-=or->length; - or=or->next; - } - - if(!or || pos==0){ - - return 0; - - }else{ - - if(pos>=or->length){ - /* exact split, or off the end? */ - if(or->next){ - - /* a split */ - *tail=or->next; - or->next=0; - - }else{ - - /* off or at the end */ - *tail=*head=0; - - } - }else{ - - /* split within a fragment */ - long lengthA=pos; - long beginB=or->begin+pos; - long lengthB=or->length-pos; - - /* make a new reference to tail the second piece */ - *tail=_fetch_ref(or->buffer->ptr.owner); - - (*tail)->buffer=or->buffer; - (*tail)->begin=beginB; - (*tail)->length=lengthB; - (*tail)->next=or->next; - _ogg_buffer_mark_one(*tail); - if(head && or==*head)*head=*tail; - - /* update the first piece */ - or->next=0; - or->length=lengthA; - - } - } - return ret; -} - -static void ogg_buffer_release_one(ogg_reference *or){ - ogg_buffer *ob=or->buffer; - ogg_buffer_state *bs=ob->ptr.owner; - - ob->refcount--; - if(ob->refcount==0){ - bs->outstanding--; /* for the returned buffer */ - ob->ptr.next=bs->unused_buffers; - bs->unused_buffers=ob; - } - - bs->outstanding--; /* for the returned reference */ - or->next=bs->unused_references; - bs->unused_references=or; - - _ogg_buffer_destroy(bs); /* lazy cleanup (if needed) */ - -} - -/* release the references, decrease the refcounts of buffers to which - they point, release any buffers with a refcount that drops to zero */ -static void ogg_buffer_release(ogg_reference *or){ - while(or){ - ogg_reference *next=or->next; - ogg_buffer_release_one(or); - or=next; - } -} - -static ogg_reference *ogg_buffer_pretruncate(ogg_reference *or,long pos){ - /* release preceeding fragments we don't want */ - while(or && pos>=or->length){ - ogg_reference *next=or->next; - pos-=or->length; - ogg_buffer_release_one(or); - or=next; - } - if (or) { - or->begin+=pos; - or->length-=pos; - } - return or; -} - -static ogg_reference *ogg_buffer_walk(ogg_reference *or){ - if(!or)return NULL; - while(or->next){ - or=or->next; - } - return(or); -} - -/* *head is appended to the front end (head) of *tail; both continue to - be valid pointers, with *tail at the tail and *head at the head */ -static ogg_reference *ogg_buffer_cat(ogg_reference *tail, ogg_reference *head){ - if(!tail)return head; - - while(tail->next){ - tail=tail->next; - } - tail->next=head; - return ogg_buffer_walk(head); -} - -static void _positionB(oggbyte_buffer *b,int pos){ - if(pos<b->pos){ - /* start at beginning, scan forward */ - b->ref=b->baseref; - b->pos=0; - b->end=b->pos+b->ref->length; - b->ptr=b->ref->buffer->data+b->ref->begin; - } -} - -static void _positionF(oggbyte_buffer *b,int pos){ - /* scan forward for position */ - while(pos>=b->end){ - /* just seek forward */ - b->pos+=b->ref->length; - b->ref=b->ref->next; - b->end=b->ref->length+b->pos; - b->ptr=b->ref->buffer->data+b->ref->begin; - } -} - -static int oggbyte_init(oggbyte_buffer *b,ogg_reference *or){ - memset(b,0,sizeof(*b)); - if(or){ - b->ref=b->baseref=or; - b->pos=0; - b->end=b->ref->length; - b->ptr=b->ref->buffer->data+b->ref->begin; - return 0; - }else - return -1; -} - -static void oggbyte_set4(oggbyte_buffer *b,ogg_uint32_t val,int pos){ - int i; - _positionB(b,pos); - for(i=0;i<4;i++){ - _positionF(b,pos); - b->ptr[pos-b->pos]=val; - val>>=8; - ++pos; - } +ogg_uint32_t ogg_page_serialno(const ogg_page *og){ + return(og->header[14] | + (og->header[15]<<8) | + (og->header[16]<<16) | + (og->header[17]<<24)); } -static unsigned char oggbyte_read1(oggbyte_buffer *b,int pos){ - _positionB(b,pos); - _positionF(b,pos); - return b->ptr[pos-b->pos]; -} - -static ogg_uint32_t oggbyte_read4(oggbyte_buffer *b,int pos){ - ogg_uint32_t ret; - _positionB(b,pos); - _positionF(b,pos); - ret=b->ptr[pos-b->pos]; - _positionF(b,++pos); - ret|=b->ptr[pos-b->pos]<<8; - _positionF(b,++pos); - ret|=b->ptr[pos-b->pos]<<16; - _positionF(b,++pos); - ret|=b->ptr[pos-b->pos]<<24; - return ret; +long ogg_page_pageno(const ogg_page *og){ + return(og->header[18] | + (og->header[19]<<8) | + (og->header[20]<<16) | + (og->header[21]<<24)); } -static ogg_int64_t oggbyte_read8(oggbyte_buffer *b,int pos){ - ogg_int64_t ret; - unsigned char t[7]; - int i; - _positionB(b,pos); - for(i=0;i<7;i++){ - _positionF(b,pos); - t[i]=b->ptr[pos++ -b->pos]; - } - _positionF(b,pos); - ret=b->ptr[pos-b->pos]; - for(i=6;i>=0;--i) - ret= ret<<8 | t[i]; +/* returns the number of packets that are completed on this page (if + the leading packet is begun on a previous page, but ends on this + page, it's counted */ - return ret; -} +/* NOTE: +If a page consists of a packet begun on a previous page, and a new +packet begun (but not completed) on this page, the return will be: + ogg_page_packets(page) ==1, + ogg_page_continued(page) !=0 -/* Now we get to the actual framing code */ - -int ogg_page_version(ogg_page *og){ - oggbyte_buffer ob; - oggbyte_init(&ob,og->header); - return oggbyte_read1(&ob,4); -} +If a page happens to be a single packet that was begun on a +previous page, and spans to the next page (in the case of a three or +more page packet), the return will be: + ogg_page_packets(page) ==0, + ogg_page_continued(page) !=0 +*/ -int ogg_page_continued(ogg_page *og){ - oggbyte_buffer ob; - oggbyte_init(&ob,og->header); - return oggbyte_read1(&ob,5)&0x01; +int ogg_page_packets(const ogg_page *og){ + int i,n=og->header[26],count=0; + for(i=0;i<n;i++) + if(og->header[27+i]<255)count++; + return(count); } -int ogg_page_bos(ogg_page *og){ - oggbyte_buffer ob; - oggbyte_init(&ob,og->header); - return oggbyte_read1(&ob,5)&0x02; -} -int ogg_page_eos(ogg_page *og){ - oggbyte_buffer ob; - oggbyte_init(&ob,og->header); - return oggbyte_read1(&ob,5)&0x04; -} +#if 0 +/* helper to initialize lookup for direct-table CRC (illustrative; we + use the static init below) */ -ogg_int64_t ogg_page_granulepos(ogg_page *og){ - oggbyte_buffer ob; - oggbyte_init(&ob,og->header); - return oggbyte_read8(&ob,6); -} +static ogg_uint32_t _ogg_crc_entry(unsigned long index){ + int i; + unsigned long r; -ogg_uint32_t ogg_page_serialno(ogg_page *og){ - oggbyte_buffer ob; - oggbyte_init(&ob,og->header); - return oggbyte_read4(&ob,14); -} - -ogg_uint32_t ogg_page_pageno(ogg_page *og){ - oggbyte_buffer ob; - oggbyte_init(&ob,og->header); - return oggbyte_read4(&ob,18); + 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); } - -/* Static CRC calculation table. See older code in CVS for dead - run-time initialization code. */ +#endif static const ogg_uint32_t crc_lookup[256] ICONST_ATTR = { 0x00000000,0x04c11db7,0x09823b6e,0x0d4326d9, @@ -538,92 +183,462 @@ static const ogg_uint32_t crc_lookup[256] ICONST_ATTR = { 0xafb010b1,0xab710d06,0xa6322bdf,0xa2f33668, 0xbcb4666d,0xb8757bda,0xb5365d03,0xb1f740b4}; -ogg_sync_state *ogg_sync_create(void){ - ogg_sync_state *oy=_ogg_calloc(1,sizeof(*oy)); - memset(oy,0,sizeof(*oy)); - oy->bufferpool=ogg_buffer_create(); - return oy; +/* init the encode/decode logical stream state */ + +int ogg_stream_init(ogg_stream_state *os,int serialno){ + if(os){ + memset(os,0,sizeof(*os)); + os->body_storage=16*1024; + os->lacing_storage=1024; + + os->body_data=_ogg_malloc(os->body_storage*sizeof(*os->body_data)); + os->lacing_vals=_ogg_malloc(os->lacing_storage*sizeof(*os->lacing_vals)); + os->granule_vals=_ogg_malloc(os->lacing_storage*sizeof(*os->granule_vals)); + + if(!os->body_data || !os->lacing_vals || !os->granule_vals){ + ogg_stream_clear(os); + return -1; + } + + os->serialno=serialno; + + return(0); + } + return(-1); +} + +/* async/delayed error detection for the ogg_stream_state */ +int ogg_stream_check(ogg_stream_state *os){ + if(!os || !os->body_data) return -1; + return 0; } -int ogg_sync_destroy(ogg_sync_state *oy){ - if(oy){ - ogg_sync_reset(oy); - ogg_buffer_destroy(oy->bufferpool); - memset(oy,0,sizeof(*oy)); - _ogg_free(oy); +/* _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)_ogg_free(os->body_data); + if(os->lacing_vals)_ogg_free(os->lacing_vals); + if(os->granule_vals)_ogg_free(os->granule_vals); + + memset(os,0,sizeof(*os)); + } + return(0); +} + +int ogg_stream_destroy(ogg_stream_state *os){ + if(os){ + ogg_stream_clear(os); + _ogg_free(os); + } + return(0); +} + +/* Helpers for ogg_stream_encode; this keeps the structure and + what's happening fairly clear */ + +static int _os_body_expand(ogg_stream_state *os,int needed){ + if(os->body_storage<=os->body_fill+needed){ + void *ret; + ret=_ogg_realloc(os->body_data,(os->body_storage+needed+1024)* + sizeof(*os->body_data)); + if(!ret){ + ogg_stream_clear(os); + return -1; + } + os->body_storage+=(needed+1024); + os->body_data=ret; } - return OGG_SUCCESS; + return 0; } -unsigned char *ogg_sync_bufferin(ogg_sync_state *oy, long bytes){ - - /* [allocate and] expose a buffer for data submission. - - If there is no head fragment - allocate one and expose it - else - if the current head fragment has sufficient unused space - expose it - else - if the current head fragment is unused - resize and expose it - else - allocate new fragment and expose it - */ - - /* base case; fifo uninitialized */ - if(!oy->fifo_head){ - oy->fifo_head=oy->fifo_tail=ogg_buffer_alloc(oy->bufferpool,bytes); - return oy->fifo_head->buffer->data; +static int _os_lacing_expand(ogg_stream_state *os,int needed){ + if(os->lacing_storage<=os->lacing_fill+needed){ + void *ret; + ret=_ogg_realloc(os->lacing_vals,(os->lacing_storage+needed+32)* + sizeof(*os->lacing_vals)); + if(!ret){ + ogg_stream_clear(os); + return -1; + } + os->lacing_vals=ret; + ret=_ogg_realloc(os->granule_vals,(os->lacing_storage+needed+32)* + sizeof(*os->granule_vals)); + if(!ret){ + ogg_stream_clear(os); + return -1; + } + os->granule_vals=ret; + os->lacing_storage+=(needed+32); } - - /* space left in current fragment case */ - if(oy->fifo_head->buffer->size- - oy->fifo_head->length- - oy->fifo_head->begin >= bytes) - return oy->fifo_head->buffer->data+ - oy->fifo_head->length+oy->fifo_head->begin; - - /* current fragment is unused, but too small */ - if(!oy->fifo_head->length){ - ogg_buffer_realloc(oy->fifo_head,bytes); - return oy->fifo_head->buffer->data+oy->fifo_head->begin; + return 0; +} + +/* checksum the page */ +/* Direct table CRC; note that this will be faster in the future if we + perform the checksum simultaneously with other copies */ + +void ogg_page_checksum_set(ogg_page *og){ + if(og){ + ogg_uint32_t crc_reg=0; + int i; + + /* safety; needed for API behavior, but not framing code */ + og->header[22]=0; + og->header[23]=0; + og->header[24]=0; + og->header[25]=0; + + 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]=(unsigned char)(crc_reg&0xff); + og->header[23]=(unsigned char)((crc_reg>>8)&0xff); + og->header[24]=(unsigned char)((crc_reg>>16)&0xff); + og->header[25]=(unsigned char)((crc_reg>>24)&0xff); } - - /* current fragment used/full; get new fragment */ +} + +#if 0 +/* submit data to the internal buffer of the framing engine */ +int ogg_stream_iovecin(ogg_stream_state *os, ogg_iovec_t *iov, int count, + long e_o_s, ogg_int64_t granulepos){ + + int bytes = 0, lacing_vals, i; + + if(ogg_stream_check(os)) return -1; + if(!iov) return 0; + + for (i = 0; i < count; ++i) bytes += (int)iov[i].iov_len; + lacing_vals=bytes/255+1; + + 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); + os->body_returned=0; + } + + /* make sure we have the buffer storage */ + if(_os_body_expand(os,bytes) || _os_lacing_expand(os,lacing_vals)) + return -1; + + /* 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 */ + + for (i = 0; i < count; ++i) { + memcpy(os->body_data+os->body_fill, iov[i].iov_base, iov[i].iov_len); + os->body_fill += (int)iov[i].iov_len; + } + + /* Store lacing vals for this packet */ + for(i=0;i<lacing_vals-1;i++){ + os->lacing_vals[os->lacing_fill+i]=255; + os->granule_vals[os->lacing_fill+i]=os->granulepos; + } + os->lacing_vals[os->lacing_fill+i]=bytes%255; + os->granulepos=os->granule_vals[os->lacing_fill+i]=granulepos; + + /* 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(e_o_s)os->e_o_s=1; + + return(0); +} + +int ogg_stream_packetin(ogg_stream_state *os,ogg_packet *op){ + ogg_iovec_t iov; + iov.iov_base = op->packet; + iov.iov_len = op->bytes; + return ogg_stream_iovecin(os, &iov, 1, op->e_o_s, op->granulepos); +} + + +/* Conditionally flush a page; force==0 will only flush nominal-size + pages, force==1 forces us to flush a page regardless of page size + so long as there's any data available at all. */ +static int ogg_stream_flush_i(ogg_stream_state *os,ogg_page *og, int force, int nfill){ + int i; + int vals=0; + int maxvals=(os->lacing_fill>255?255:os->lacing_fill); + int bytes=0; + long acc=0; + ogg_int64_t granule_pos=-1; + + if(ogg_stream_check(os)) return(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 */ + granule_pos=0; + for(vals=0;vals<maxvals;vals++){ + if((os->lacing_vals[vals]&0x0ff)<255){ + vals++; + break; + } + } + }else{ + + /* The extra packets_done, packet_just_done logic here attempts to do two things: + 1) Don't unneccessarily span pages. + 2) Unless necessary, don't flush pages if there are less than four packets on + them; this expands page size to reduce unneccessary overhead if incoming packets + are large. + These are not necessary behaviors, just 'always better than naive flushing' + without requiring an application to explicitly request a specific optimized + behavior. We'll want an explicit behavior setup pathway eventually as well. */ + + int packets_done=0; + int packet_just_done=0; + for(vals=0;vals<maxvals;vals++){ + if(acc>nfill && packet_just_done>=4){ + force=1; + break; + } + acc+=os->lacing_vals[vals]&0x0ff; + if((os->lacing_vals[vals]&0xff)<255){ + granule_pos=os->granule_vals[vals]; + packet_just_done=++packets_done; + }else + packet_just_done=0; + } + if(vals==255)force=1; + } + + if(!force) return(0); + + /* 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]=(unsigned char)(granule_pos&0xff); + granule_pos>>=8; + } + + /* 32 bits of stream serial number */ + { + long serialno=os->serialno; + for(i=14;i<18;i++){ + os->header[i]=(unsigned char)(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 */ { - ogg_reference *new=ogg_buffer_alloc(oy->bufferpool,bytes); - oy->fifo_head->next=new; - oy->fifo_head=new; + long pageno=os->pageno++; + for(i=18;i<22;i++){ + os->header[i]=(unsigned char)(pageno&0xff); + pageno>>=8; + } } - return oy->fifo_head->buffer->data; + + /* 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]=(unsigned char)(vals&0xff); + for(i=0;i<vals;i++) + bytes+=os->header[i+27]=(unsigned char)(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(*os->lacing_vals)); + memmove(os->granule_vals,os->granule_vals+vals,os->lacing_fill*sizeof(*os->granule_vals)); + os->body_returned+=bytes; + + /* calculate the checksum */ + + ogg_page_checksum_set(og); + + /* done */ + return(1); } -int ogg_sync_wrote(ogg_sync_state *oy, long bytes){ - if(!oy->fifo_head)return OGG_EINVAL; - if(oy->fifo_head->buffer->size-oy->fifo_head->length-oy->fifo_head->begin < - bytes)return OGG_EINVAL; - oy->fifo_head->length+=bytes; - oy->fifo_fill+=bytes; - return OGG_SUCCESS; +/* 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 guarantee that all packets have flushed. + Only a return value of 0 from ogg_stream_flush indicates all packet + data is flushed into pages. + + since ogg_stream_flush 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 specifically need to flush + an page regardless of size in the middle of a stream. */ + +int ogg_stream_flush(ogg_stream_state *os,ogg_page *og){ + return ogg_stream_flush_i(os,og,1,4096); +} + +/* 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){ + int force=0; + if(ogg_stream_check(os)) return 0; + + if((os->e_o_s&&os->lacing_fill) || /* 'were done, now flush' case */ + (os->lacing_fill&&!os->b_o_s)) /* 'initial header page' case */ + force=1; + + return(ogg_stream_flush_i(os,og,force,4096)); } -static ogg_uint32_t _checksum(ogg_reference *or, int bytes){ - ogg_uint32_t crc_reg=0; - int j,post; - - while(or){ - unsigned char *data=or->buffer->data+or->begin; - post=(bytes<or->length?bytes:or->length); - for(j=0;j<post;++j) - crc_reg=(crc_reg<<8)^crc_lookup[((crc_reg >> 24)&0xff)^data[j]]; - bytes-=j; - or=or->next; +/* Like the above, but an argument is provided to adjust the nominal +page size for applications which are smart enough to provide their +own delay based flushing */ + +int ogg_stream_pageout_fill(ogg_stream_state *os, ogg_page *og, int nfill){ + int force=0; + if(ogg_stream_check(os)) return 0; + + if((os->e_o_s&&os->lacing_fill) || /* 'were done, now flush' case */ + (os->lacing_fill&&!os->b_o_s)) /* 'initial header page' case */ + force=1; + + return(ogg_stream_flush_i(os,og,force,nfill)); +} + +int ogg_stream_eos(ogg_stream_state *os){ + if(ogg_stream_check(os)) return 1; + return os->e_o_s; +} +#endif +/* 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. */ + +/* initialize the struct to a known state */ +int ogg_sync_init(ogg_sync_state *oy){ + if(oy){ + oy->storage = -1; /* used as a readiness flag */ + memset(oy,0,sizeof(*oy)); } + return(0); +} + +/* clear non-flat storage within */ +int ogg_sync_clear(ogg_sync_state *oy){ + if(oy){ + if(oy->data)_ogg_free(oy->data); + memset(oy,0,sizeof(*oy)); + } + return(0); +} + +int ogg_sync_destroy(ogg_sync_state *oy){ + if(oy){ + ogg_sync_clear(oy); + _ogg_free(oy); + } + return(0); +} - return crc_reg; +int ogg_sync_check(ogg_sync_state *oy){ + if(oy->storage<0) return -1; + return 0; } +char *ogg_sync_buffer(ogg_sync_state *oy, long size){ + if(ogg_sync_check(oy)) return NULL; + + /* 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); + 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 */ + void *ret; + + if(oy->data) + ret=_ogg_realloc(oy->data,newsize); + else + ret=_ogg_malloc(newsize); + if(!ret){ + ogg_sync_clear(oy); + return NULL; + } + oy->data=ret; + 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(ogg_sync_check(oy))return -1; + 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. @@ -636,248 +651,130 @@ static ogg_uint32_t _checksum(ogg_reference *or, int bytes){ */ long ogg_sync_pageseek(ogg_sync_state *oy,ogg_page *og){ - oggbyte_buffer page; - long bytes,ret=0; - - ogg_page_release(og); - - bytes=oy->fifo_fill; - oggbyte_init(&page,oy->fifo_tail); + unsigned char *page=oy->data+oy->returned; + unsigned char *next; + long bytes=oy->fill-oy->returned; + if(ogg_sync_check(oy))return 0; + if(oy->headerbytes==0){ - if(bytes<27)goto sync_out; /* not enough for even a minimal header */ + int headerbytes,i; + if(bytes<27)return(0); /* not enough for a header */ /* verify capture pattern */ - if(oggbyte_read1(&page,0)!=(int)'O' || - oggbyte_read1(&page,1)!=(int)'g' || - oggbyte_read1(&page,2)!=(int)'g' || - oggbyte_read1(&page,3)!=(int)'S' ) goto sync_fail; - - oy->headerbytes=oggbyte_read1(&page,26)+27; - } - if(bytes<oy->headerbytes)goto sync_out; /* not enough for header + - seg table */ - if(oy->bodybytes==0){ - int i; + 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<oy->headerbytes-27;i++) - oy->bodybytes+=oggbyte_read1(&page,27+i); + + for(i=0;i<page[26];i++) + oy->bodybytes+=page[27+i]; + oy->headerbytes=headerbytes; } - if(oy->bodybytes+oy->headerbytes>bytes)goto sync_out; - - /* we have what appears to be a complete page; last test: verify - checksum */ + if(oy->bodybytes+oy->headerbytes>bytes)return(0); + + /* The whole test page is buffered. Verify the checksum */ { - ogg_uint32_t chksum=oggbyte_read4(&page,22); - oggbyte_set4(&page,0,22); - - /* Compare checksums; memory continues to be common access */ - if(chksum!=_checksum(oy->fifo_tail,oy->bodybytes+oy->headerbytes)){ - + /* 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; + ogg_page_checksum_set(&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; remember all the memory is common access */ + at all) */ + /* replace the computed checksum with the one actually read in */ + memcpy(page+22,chksum,4); - oggbyte_set4(&page,chksum,22); + /* Bad checksum. Lose sync */ goto sync_fail; } - oggbyte_set4(&page,chksum,22); - } - - /* We have a page. Set up page return. */ - if(og){ - /* set up page output */ - og->header=ogg_buffer_split(&oy->fifo_tail,&oy->fifo_head,oy->headerbytes); - og->header_len=oy->headerbytes; - og->body=ogg_buffer_split(&oy->fifo_tail,&oy->fifo_head,oy->bodybytes); - og->body_len=oy->bodybytes; - }else{ - /* simply advance */ - oy->fifo_tail= - ogg_buffer_pretruncate(oy->fifo_tail,oy->headerbytes+oy->bodybytes); - if(!oy->fifo_tail)oy->fifo_head=0; } - ret=oy->headerbytes+oy->bodybytes; - oy->unsynced=0; - oy->headerbytes=0; - oy->bodybytes=0; - oy->fifo_fill-=ret; + /* 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; + } - return ret; + 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; - oy->fifo_tail=ogg_buffer_pretruncate(oy->fifo_tail,1); - ret--; - /* search forward through fragments for possible capture */ - while(oy->fifo_tail){ - /* invariant: fifo_cursor points to a position in fifo_tail */ - unsigned char *now=oy->fifo_tail->buffer->data+oy->fifo_tail->begin; - unsigned char *next=memchr(now, 'O', oy->fifo_tail->length); - - if(next){ - /* possible capture in this segment */ - long bytes=next-now; - oy->fifo_tail=ogg_buffer_pretruncate(oy->fifo_tail,bytes); - ret-=bytes; - break; - }else{ - /* no capture. advance to next segment */ - long bytes=oy->fifo_tail->length; - ret-=bytes; - oy->fifo_tail=ogg_buffer_pretruncate(oy->fifo_tail,bytes); - } - } - if(!oy->fifo_tail)oy->fifo_head=0; - oy->fifo_fill+=ret; + /* search for possible capture */ + next=memchr(page+1,'O',bytes-1); + if(!next) + next=oy->data+oy->fill; - sync_out: - return ret; + oy->returned=(int)(next-oy->data); + return((long)-(next-page)); } -/* clear things to an initial state. Good to call, eg, before seeking */ -int ogg_sync_reset(ogg_sync_state *oy){ +/* sync the stream and get a page. Keep trying until we find a page. + Suppress 'sync errors' after reporting the first. - ogg_buffer_release(oy->fifo_tail); - oy->fifo_tail=0; - oy->fifo_head=0; - oy->fifo_fill=0; + return values: + -1) recapture (hole in data) + 0) need more data + 1) page returned - oy->unsynced=0; - oy->headerbytes=0; - oy->bodybytes=0; - return OGG_SUCCESS; -} + Returns pointers into buffered data; invalidated by next call to + _stream, _clear, _init, or _buffer */ -ogg_stream_state *ogg_stream_create(int serialno){ - ogg_stream_state *os=_ogg_calloc(1,sizeof(*os)); - os->serialno=serialno; - os->pageno=-1; - return os; -} +int ogg_sync_pageout(ogg_sync_state *oy, ogg_page *og){ -int ogg_stream_destroy(ogg_stream_state *os){ - if(os){ - ogg_buffer_release(os->header_tail); - ogg_buffer_release(os->body_tail); - memset(os,0,sizeof(*os)); - _ogg_free(os); - } - return OGG_SUCCESS; -} + if(ogg_sync_check(oy))return 0; + /* 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 */ -#define FINFLAG 0x80000000UL -#define FINMASK 0x7fffffffUL - -static void _next_lace(oggbyte_buffer *ob,ogg_stream_state *os){ - /* search ahead one lace */ - os->body_fill_next=0; - while(os->laceptr<os->lacing_fill){ - int val=oggbyte_read1(ob,27+os->laceptr++); - os->body_fill_next+=val; - if(val<255){ - os->body_fill_next|=FINFLAG; - os->clearflag=1; - break; + for(;;){ + long ret=ogg_sync_pageseek(oy,og); + if(ret>0){ + /* have a page */ + return(1); + } + if(ret==0){ + /* need more data */ + return(0); } - } -} - -STATICIRAM_NOT_MDCT void _span_queued_page(ogg_stream_state *os) - ICODE_ATTR_TREMOR_NOT_MDCT; -STATICIRAM_NOT_MDCT void _span_queued_page(ogg_stream_state *os){ - while( !(os->body_fill&FINFLAG) ){ - - if(!os->header_tail)break; - - /* first flush out preceeding page header (if any). Body is - flushed as it's consumed, so that's not done here. */ - - if(os->lacing_fill>=0) - os->header_tail=ogg_buffer_pretruncate(os->header_tail, - os->lacing_fill+27); - os->lacing_fill=0; - os->laceptr=0; - os->clearflag=0; - - if(!os->header_tail){ - os->header_head=0; - break; - }else{ - - /* process/prepare next page, if any */ - - long pageno; - oggbyte_buffer ob; - ogg_page og; /* only for parsing header values */ - og.header=os->header_tail; /* only for parsing header values */ - pageno=ogg_page_pageno(&og); - - oggbyte_init(&ob,os->header_tail); - os->lacing_fill=oggbyte_read1(&ob,26); - - /* are we in sequence? */ - if(pageno!=os->pageno){ - if(os->pageno==-1) /* indicates seek or reset */ - os->holeflag=1; /* set for internal use */ - else - os->holeflag=2; /* set for external reporting */ - - os->body_tail=ogg_buffer_pretruncate(os->body_tail, - os->body_fill); - if(os->body_tail==0)os->body_head=0; - os->body_fill=0; - - } - if(ogg_page_continued(&og)){ - if(os->body_fill==0){ - /* continued packet, but no preceeding data to continue */ - /* dump the first partial packet on the page */ - _next_lace(&ob,os); - os->body_tail= - ogg_buffer_pretruncate(os->body_tail,os->body_fill_next&FINMASK); - if(os->body_tail==0)os->body_head=0; - /* set span flag */ - if(!os->spanflag && !os->holeflag)os->spanflag=2; - } - }else{ - if(os->body_fill>0){ - /* preceeding data to continue, but not a continued page */ - /* dump body_fill */ - os->body_tail=ogg_buffer_pretruncate(os->body_tail, - os->body_fill); - if(os->body_tail==0)os->body_head=0; - os->body_fill=0; - - /* set espan flag */ - if(!os->spanflag && !os->holeflag)os->spanflag=2; - } - } - - if(os->laceptr<os->lacing_fill){ - os->granulepos=ogg_page_granulepos(&og); + /* head did not start a synced page... skipped some bytes */ + if(!oy->unsynced){ + oy->unsynced=1; + return(-1); + } - /* get current packet size & flag */ - _next_lace(&ob,os); - os->body_fill+=os->body_fill_next; /* addition handles the flag fine; - unsigned on purpose */ - /* ...and next packet size & flag */ - _next_lace(&ob,os); + /* loop. keep looking */ - } - - os->pageno=pageno+1; - os->e_o_s=ogg_page_eos(&og); - os->b_o_s=ogg_page_bos(&og); - - } } } @@ -885,45 +782,156 @@ STATICIRAM_NOT_MDCT void _span_queued_page(ogg_stream_state *os){ 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 serialno=ogg_page_serialno(og); int version=ogg_page_version(og); + int continued=ogg_page_continued(og); + int bos=ogg_page_bos(og); + int eos=ogg_page_eos(og); + ogg_int64_t granulepos=ogg_page_granulepos(og); + ogg_uint32_t serialno=ogg_page_serialno(og); + long pageno=ogg_page_pageno(og); + int segments=header[26]; + + if(ogg_stream_check(os)) return -1; + + /* 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(*os->lacing_vals)); + memmove(os->granule_vals,os->granule_vals+lr, + (os->lacing_fill-lr)*sizeof(*os->granule_vals)); + } + os->lacing_fill-=lr; + os->lacing_packet-=lr; + os->lacing_returned=0; + } + } /* check the serial number */ - if(serialno!=os->serialno){ - ogg_page_release(og); - return OGG_ESERIAL; + if(serialno!=os->serialno)return(-1); + if(version>0)return(-1); + + if(_os_lacing_expand(os,segments+1)) return -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 may need to skip + some segments */ + if(continued){ + if(os->lacing_fill<1 || + os->lacing_vals[os->lacing_fill-1]==0x400){ + bos=0; + for(;segptr<segments;segptr++){ + int val=header[27+segptr]; + body+=val; + bodysize-=val; + if(val<255){ + segptr++; + break; + } + } + } } - if(version>0){ - ogg_page_release(og); - return OGG_EVERSION; + + if(bodysize){ + if(_os_body_expand(os,bodysize)) return -1; + memcpy(os->body_data+os->body_fill,body,bodysize); + os->body_fill+=bodysize; } - /* add to fifos */ - if(!os->body_tail){ - os->body_tail=og->body; - os->body_head=ogg_buffer_walk(og->body); - }else{ - os->body_head=ogg_buffer_cat(os->body_head,og->body); + { + int saved=-1; + while(segptr<segments){ + int val=header[27+segptr]; + os->lacing_vals[os->lacing_fill]=val; + os->granule_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 granulepos on the last granuleval of the last full packet */ + if(saved!=-1){ + os->granule_vals[saved]=granulepos; + } + } - if(!os->header_tail){ - os->header_tail=og->header; - os->header_head=ogg_buffer_walk(og->header); - os->lacing_fill=-27; - }else{ - os->header_head=ogg_buffer_cat(os->header_head,og->header); + + if(eos){ + os->e_o_s=1; + if(os->lacing_fill>0) + os->lacing_vals[os->lacing_fill-1]|=0x200; } - memset(og,0,sizeof(*og)); - return OGG_SUCCESS; + 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){ + if(ogg_sync_check(oy))return -1; + + 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){ + if(ogg_stream_check(os)) return -1; + + os->body_fill=0; + os->body_returned=0; - ogg_buffer_release(os->header_tail); - ogg_buffer_release(os->body_tail); - os->header_tail=os->header_head=0; - os->body_tail=os->body_head=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; @@ -931,137 +939,1159 @@ int ogg_stream_reset(ogg_stream_state *os){ os->packetno=0; os->granulepos=0; - os->body_fill=0; - os->lacing_fill=0; - - os->holeflag=0; - os->spanflag=0; - os->clearflag=0; - os->laceptr=0; - os->body_fill_next=0; - - return OGG_SUCCESS; + return(0); } int ogg_stream_reset_serialno(ogg_stream_state *os,int serialno){ + if(ogg_stream_check(os)) return -1; ogg_stream_reset(os); os->serialno=serialno; - return OGG_SUCCESS; + return(0); } -STATICIRAM_NOT_MDCT int _packetout(ogg_stream_state *os,ogg_packet *op,int adv) +static int _packetout(ogg_stream_state *os,ogg_packet *op,int adv) ICODE_ATTR_TREMOR_NOT_MDCT; -STATICIRAM_NOT_MDCT int _packetout(ogg_stream_state *os,ogg_packet *op,int adv){ +static int _packetout(ogg_stream_state *os,ogg_packet *op,int adv){ - ogg_packet_release(op); - _span_queued_page(os); + /* 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) */ - if(os->holeflag){ - int temp=os->holeflag; - if(os->clearflag) - os->holeflag=0; - else - os->holeflag=1; - if(temp==2){ - os->packetno++; - return OGG_HOLE; - } - } - if(os->spanflag){ - int temp=os->spanflag; - if(os->clearflag) - os->spanflag=0; - else - os->spanflag=1; - if(temp==2){ - os->packetno++; - return OGG_SPAN; - } + int ptr=os->lacing_returned; + + if(os->lacing_packet<=ptr)return(0); + + if(os->lacing_vals[ptr]&0x400){ + /* we need to tell the codec there's a gap; it might need to + handle previous packet dependencies. */ + os->lacing_returned++; + os->packetno++; + return(-1); } - if(!(os->body_fill&FINFLAG)) return 0; - if(!op && !adv)return 1; /* just using peek as an inexpensive way + if(!op && !adv)return(1); /* just using peek as an inexpensive way to ask if there's a whole packet waiting */ - if(op){ - op->b_o_s=os->b_o_s; - if(os->e_o_s && os->body_fill_next==0) - op->e_o_s=os->e_o_s; - else - op->e_o_s=0; - if( (os->body_fill&FINFLAG) && !(os->body_fill_next&FINFLAG) ) - op->granulepos=os->granulepos; - else - op->granulepos=-1; - op->packetno=os->packetno; - } - if(adv){ - oggbyte_buffer ob; - oggbyte_init(&ob,os->header_tail); + /* Gather the whole packet. We'll have no holes or a partial packet */ + { + int size=os->lacing_vals[ptr]&0xff; + long bytes=size; + int eos=os->lacing_vals[ptr]&0x200; /* last packet of the stream? */ + int bos=os->lacing_vals[ptr]&0x100; /* first packet of the stream? */ + + while(size==255){ + int val=os->lacing_vals[++ptr]; + size=val&0xff; + if(val&0x200)eos=0x200; + bytes+=size; + } - /* split the body contents off */ if(op){ - op->packet=ogg_buffer_split(&os->body_tail,&os->body_head, - os->body_fill&FINMASK); - op->bytes=os->body_fill&FINMASK; - }else{ - os->body_tail=ogg_buffer_pretruncate(os->body_tail, - os->body_fill&FINMASK); - if(os->body_tail==0)os->body_head=0; + op->e_o_s=eos; + op->b_o_s=bos; + op->packet=os->body_data+os->body_returned; + op->packetno=os->packetno; + op->granulepos=os->granule_vals[ptr]; + op->bytes=bytes; } - /* update lacing pointers */ - os->body_fill=os->body_fill_next; - _next_lace(&ob,os); - }else{ - if(op){ - op->packet=ogg_buffer_sub(os->body_tail,0,os->body_fill&FINMASK); - op->bytes=os->body_fill&FINMASK; + if(adv){ + os->body_returned+=bytes; + os->lacing_returned=ptr+1; + os->packetno++; } } - - if(adv){ - os->packetno++; - os->b_o_s=0; - } - - return 1; + return(1); } int ogg_stream_packetout(ogg_stream_state *os,ogg_packet *op) ICODE_ATTR_TREMOR_NOT_MDCT; int ogg_stream_packetout(ogg_stream_state *os,ogg_packet *op){ + if(ogg_stream_check(os)) return 0; return _packetout(os,op,1); } int ogg_stream_packetpeek(ogg_stream_state *os,ogg_packet *op) ICODE_ATTR_TREMOR_NOT_MDCT; int ogg_stream_packetpeek(ogg_stream_state *os,ogg_packet *op){ + if(ogg_stream_check(os)) return 0; return _packetout(os,op,0); } -int ogg_packet_release(ogg_packet *op) { - if(op){ - ogg_buffer_release(op->packet); - memset(op, 0, sizeof(*op)); +void ogg_packet_clear(ogg_packet *op) { + _ogg_free(op->packet); + memset(op, 0, sizeof(*op)); +} + +#ifdef _V_SELFTEST +#include <stdio.h> + +ogg_stream_state os_en, os_de; +ogg_sync_state oy; + +void checkpacket(ogg_packet *op,long len, int no, long pos){ + long j; + static int sequence=0; + static int lastno=0; + + if(op->bytes!=len){ + fprintf(stderr,"incorrect packet length (%ld != %ld)!\n",op->bytes,len); + exit(1); + } + if(op->granulepos!=pos){ + fprintf(stderr,"incorrect packet granpos (%ld != %ld)!\n",(long)op->granulepos,pos); + 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); } - return OGG_SUCCESS; + + /* 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); + } } -int ogg_page_release(ogg_page *og) { - if(og){ - ogg_buffer_release(og->header); - ogg_buffer_release(og->body); - memset(og, 0, sizeof(*og)); +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); + } } - return OGG_SUCCESS; + 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," granulepos: %d serialno: %d pageno: %ld\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], + ((long)(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 ogg_page_dup(ogg_page *dup,ogg_page *orig){ - dup->header_len=orig->header_len; - dup->body_len=orig->body_len; - dup->header=ogg_buffer_dup(orig->header); - dup->body=ogg_buffer_dup(orig->body); +void copy_page(ogg_page *og){ + unsigned char *temp=_ogg_malloc(og->header_len); + memcpy(temp,og->header,og->header_len); + og->header=temp; + + temp=_ogg_malloc(og->body_len); + memcpy(temp,og->body,og->body_len); + og->body=temp; +} + +void free_page(ogg_page *og){ + _ogg_free (og->header); + _ogg_free (og->body); +} + +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, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0xf8,0x3c,0x19,0x79, + 255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,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, + 0x38,0xe6,0xb6,0x28, + 6, + 255,220,255,4,255,0}; + + +/* spill expansion test */ +const int head1_4b[] = {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_4b[] = {0x4f,0x67,0x67,0x53,0,0x00, + 0x07,0x10,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0xce,0x8f,0x17,0x1a, + 23, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,10,255,4,255,0,0}; + + +const int head3_4b[] = {0x4f,0x67,0x67,0x53,0,0x04, + 0x07,0x14,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,2,0,0,0, + 0x9b,0xb2,0x50,0xa1, + 1, + 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, + 0x68,0x22,0x7c,0x3d, + 255, + 100, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,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, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0x01,0x02,0x03,0x04,2,0,0,0, + 0xf4,0x87,0xba,0xf3, + 255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,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, + 0xf7,0x2f,0x6c,0x60, + 5, + 254,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, + 0x68,0x22,0x7c,0x3d, + 255, + 100, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,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, int byteskip, + int pageskip, int packetskip){ + unsigned char *data=_ogg_malloc(1024*1024); /* for scripted test cases only */ + long inptr=0; + long outptr=0; + long deptr=0; + long depacket=0; + long granule_pos=7,pageno=0; + int i,j,packets,pageout=pageskip; + int eosflag=0; + int bosflag=0; + + int byteskipcount=0; + + ogg_stream_reset(&os_en); + ogg_stream_reset(&os_de); + ogg_sync_reset(&oy); + + for(packets=0;packets<packetskip;packets++) + depacket+=pl[packets]; + + 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.granulepos=granule_pos; + + granule_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,"%ld, ",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++; + if(pageskip){ + bosflag=1; + pageskip--; + deptr+=og.body_len; + } + + /* have a complete page; submit it to sync/decode */ + + { + ogg_page og_de; + ogg_packet op_de,op_de2; + char *buf=ogg_sync_buffer(&oy,og.header_len+og.body_len); + char *next=buf; + byteskipcount+=og.header_len; + if(byteskipcount>byteskip){ + memcpy(next,og.header,byteskipcount-byteskip); + next+=byteskipcount-byteskip; + byteskipcount=byteskip; + } + + byteskipcount+=og.body_len; + if(byteskipcount>byteskip){ + memcpy(next,og.body,byteskipcount-byteskip); + next+=byteskipcount-byteskip; + byteskipcount=byteskip; + } + + ogg_sync_wrote(&oy,next-buf); + + while(1){ + int ret=ogg_sync_pageout(&oy,&og_de); + if(ret==0)break; + if(ret<0)continue; + /* got a page. Happy happy. Verify that it's good. */ + + fprintf(stderr,"(%d), ",pageout); + + 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_packetpeek(&os_de,&op_de2)>0){ + ogg_stream_packetpeek(&os_de,NULL); + ogg_stream_packetout(&os_de,&op_de); /* just catching them all */ + + /* verify peek and out match */ + if(memcmp(&op_de,&op_de2,sizeof(op_de))){ + fprintf(stderr,"packetout != packetpeek! pos=%ld\n", + depacket); + exit(1); + } + + /* 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 granulepos flag */ + if(op_de.granulepos!=-1){ + fprintf(stderr," granule:%ld ",(long)op_de.granulepos); + } + } + } + } + } + } + } + _ogg_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,0,0,0); + } + + { + /* 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,0,0,0); + } + + { + /* 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,0,0,0); + } + + { + /* 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,0,0,0); + } + + { + /* continuing packet test; with page spill expansion, we have to + overflow the lacing table. */ + const int packets[]={0,65500,259,255,-1}; + const int *headret[]={head1_4,head2_4,head3_4,NULL}; + + fprintf(stderr,"testing single packet page span... "); + test_pack(packets,headret,0,0,0); + } + + { + /* spill expand packet test */ + const int packets[]={0,4345,259,255,0,0,-1}; + const int *headret[]={head1_4b,head2_4b,head3_4b,NULL}; + + fprintf(stderr,"testing page spill expansion... "); + test_pack(packets,headret,0,0,0); + } + + /* 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,0,0,0); + } + + { + /* packet that overspans over an entire page */ + const int packets[]={0,100,130049,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,0,0,0); + } + + { + /* test for the libogg 1.1.1 resync in large continuation bug + found by Josh Coalson) */ + const int packets[]={0,100,130049,259,255,-1}; + const int *headret[]={head1_6,head2_6,head3_6,head4_6,NULL}; + + fprintf(stderr,"testing continuation resync in very large packets... "); + test_pack(packets,headret,100,2,3); + } + + { + /* term only page. why not? */ + const int packets[]={0,100,64770,-1}; + const int *headret[]={head1_7,head2_7,head3_7,NULL}; + + fprintf(stderr,"testing zero data page (1 nil packet)... "); + test_pack(packets,headret,0,0,0); + } + + + + { + /* build a bunch of pages for testing */ + unsigned char *data=_ogg_malloc(1024*1024); + int pl[]={0, 1,1,98,4079, 1,1,2954,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.granulepos=(i+1)*1000; + + for(j=0;j<len;j++)data[inptr++]=i+j; + ogg_stream_packetin(&os_en,&op); + } + + _ogg_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,1,1,-1); + if(ogg_stream_packetout(&os_de,&test)!=1)error(); + checkpacket(&test,1,2,-1); + if(ogg_stream_packetout(&os_de,&test)!=1)error(); + checkpacket(&test,98,3,-1); + if(ogg_stream_packetout(&os_de,&test)!=1)error(); + checkpacket(&test,4079,4,5000); + 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,9,-1); + if(ogg_stream_packetout(&os_de,&test)!=1)error(); + checkpacket(&test,34,10,-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,1,1,-1); + if(ogg_stream_packetout(&os_de,&test)!=1)error(); + checkpacket(&test,1,2,-1); + if(ogg_stream_packetout(&os_de,&test)!=1)error(); + checkpacket(&test,98,3,-1); + if(ogg_stream_packetout(&os_de,&test)!=1)error(); + checkpacket(&test,4079,4,5000); + if(ogg_stream_packetout(&os_de,&test)!=1)error(); + checkpacket(&test,1,5,-1); + if(ogg_stream_packetout(&os_de,&test)!=1)error(); + checkpacket(&test,1,6,-1); + if(ogg_stream_packetout(&os_de,&test)!=1)error(); + checkpacket(&test,2954,7,-1); + if(ogg_stream_packetout(&os_de,&test)!=1)error(); + checkpacket(&test,2057,8,9000); + 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,17,18000); + 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"); + } + + /* Free page data that was previously copied */ + { + for(i=0;i<5;i++){ + free_page(&og[i]); + } + } + } + + return(0); } +#endif + + + + |