blob: 8d2c0ae2049ce8b6107f6e71540568fce29f0ecb [file] [log] [blame]
/********************************************************************
* *
* THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. *
* USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS *
* GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE *
* IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. *
* *
* THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2003 *
* by the Xiph.Org Foundation http://www.xiph.org/ *
* *
********************************************************************
function: code raw packets into framed Ogg logical stream and
decode Ogg logical streams back into raw packets
last mod: $Id: stream.c,v 1.1.2.14 2003/11/30 15:56:32 arc Exp $
********************************************************************/
#include <stdlib.h>
#include <string.h>
#include "ogginternal.h" /* proper way to suck in ogg/ogg.h from a
libogg compile */
/* A complete description of Ogg framing exists in docs/framing.html */
ogg_stream_state *ogg_stream_create(int serialno){
ogg_stream_state *os=_ogg_calloc(1,sizeof(*os));
os->watermark=4096;
os->serialno=serialno;
os->bufferpool=ogg_buffer_create();
return os;
}
int ogg_stream_setfill(ogg_stream_state *os,int watermark){
if(os){
if(watermark>65535)watermark=65535;
os->watermark=watermark;
return watermark;
}
return OGG_EINVAL;
}
/* _clear does not free os, only the non-flat storage within */
int ogg_stream_destroy(ogg_stream_state *os){
if(os){
ogg_buffer_release(os->header_tail);
ogg_buffer_release(os->body_tail);
oggbyte_clear(&os->header_build);
ogg_buffer_destroy(os->bufferpool);
memset(os,0,sizeof(*os));
}
return OGG_SUCCESS;
}
/* finish building a header then flush the current page header and
body to the output buffer */
static void _packet_flush(ogg_stream_state *os,int nextcomplete){
oggbyte_buffer *obb=&os->header_build;
unsigned char ctemp;
if(os->lacing_fill){
/* build header */
oggbyte_set1(obb,'O',0);
oggbyte_set1(obb,'g',1);
oggbyte_set1(obb,'g',2);
oggbyte_set1(obb,'S',3);
oggbyte_set1(obb,0x00,4); /* stream structure version */
ctemp=0x00;
if(os->continued)ctemp|=0x01; /* continued packet flag? */
os->continued=nextcomplete;
if(os->b_o_s==0)ctemp|=0x02; /* first page flag? */
if(os->e_o_s)ctemp|=0x04; /* last page flag? */
oggbyte_set1(obb,ctemp,5);
/* 64 bits of PCM position */
if(!os->b_o_s)
oggbyte_set8(obb,0,6);
else
oggbyte_set8(obb,os->granulepos,6);
os->b_o_s=1;
/* 32 bits of stream serial number */
oggbyte_set4(obb,os->serialno,14);
/* 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 */
oggbyte_set4(obb,os->pageno++,18);
/* CRC filled in later */
/* segment table size */
oggbyte_set1(obb,os->lacing_fill,26);
/* toss the header on the fifo */
if(os->header_tail){
ogg_reference *ret=oggbyte_return_and_reset(&os->header_build);
os->header_head=ogg_buffer_cat(os->header_head,ret);
if(nextcomplete)oggbyte_init(&os->header_build,0,os->bufferpool);
}else{
os->header_tail=oggbyte_return_and_reset(&os->header_build);
os->header_head=ogg_buffer_walk(os->header_tail);
if(nextcomplete)oggbyte_init(&os->header_build,0,os->bufferpool);
}
os->lacing_fill=0;
os->body_fill=0;
}
}
/* submit data to the internal buffer of the framing engine */
int ogg_stream_packetin(ogg_stream_state *os,ogg_packet *op){
/* get sizing */
long bytes=ogg_buffer_length(op->packet);
long lacing_vals=bytes/255+1;
int remainder=bytes%255;
int i;
if(os->e_o_s){
ogg_packet_release(op);
return OGG_EEOS;
}
if(!os->lacing_fill)
oggbyte_init(&os->header_build,0,os->bufferpool);
/* concat packet data */
if(os->body_head)
os->body_head=ogg_buffer_cat(os->body_head,op->packet);
else
os->body_tail=os->body_head=op->packet;
/* add lacing vals, but finish/flush packet first if we hit a
(watermark && not initial page) */
for(i=0;i<lacing_vals-1;i++){ /* handle the 255s first */
os->body_fill+=255;
oggbyte_set1(&os->header_build,255,27+os->lacing_fill++);
if(os->body_fill>=os->watermark && os->b_o_s)_packet_flush(os,1);
if(os->lacing_fill==255)_packet_flush(os,1);
}
/* we know we'll finish this packet on this page; propogate
granulepos et al and then finish packet lacing */
os->body_fill+=remainder;
os->granulepos=op->granulepos;
os->packetno++; /* for the sake of completeness */
if(op->e_o_s)os->e_o_s=1;
oggbyte_set1(&os->header_build,remainder,27+os->lacing_fill++);
if(os->e_o_s ||
os->body_fill>=os->watermark ||
!os->b_o_s ||
os->lacing_fill==255)_packet_flush(os,0);
memset(op,0,sizeof(*op));
return OGG_SUCCESS;
}
/* This constructs pages from buffered packet segments. */
int ogg_stream_pageout(ogg_stream_state *os, ogg_page *og){
oggbyte_buffer ob;
long header_bytes;
long body_bytes=0;
int i;
/* if the incoming page is still valid (and thus unconsumed),
release it to prevent a leak */
ogg_page_release(og);
/* is there a page waiting to come back? */
if(!os->header_tail) return 0;
/* get header and body sizes */
oggbyte_init(&ob,os->header_tail,0);
header_bytes=oggbyte_read1(&ob,26)+27;
for(i=27;i<header_bytes;i++)
body_bytes+=oggbyte_read1(&ob,i);
/* split page references out of the fifos */
if(og){
og->header=ogg_buffer_split(&os->header_tail,&os->header_head,header_bytes);
og->header_len=header_bytes;
og->body=ogg_buffer_split(&os->body_tail,&os->body_head,body_bytes);
og->body_len=body_bytes;
/* checksum */
ogg_page_checksum_set(og);
}else{
os->header_tail=ogg_buffer_pretruncate(os->header_tail,header_bytes);
os->body_tail=ogg_buffer_pretruncate(os->body_tail,body_bytes);
if(!os->header_tail)os->header_head=0;
if(!os->body_tail)os->body_head=0;
}
return 1;
}
/* 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){
/* If there's no page already waiting for output, flush a partial
page... assuming we have one */
if(!os->header_tail)_packet_flush(os,0);
return ogg_stream_pageout(os,og);
}
int ogg_stream_eos(ogg_stream_state *os){
return os->e_o_s;
}
/* DECODING PRIMITIVES: packet streaming layer **********************/
#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;
}
}
}
/* sync and reporting within a logical stream uses a flagging system
to improve the utility of the information coming back. There are
two basic problems a stream can run into; missing pages (a hole in
the page sequence numbering), and malformed pages such that
spanning isn't handled properly. Both need to be reported.
OGG_EHOLE happens when a page is out of sequence. However, this
can be a natural case after seeking or reset and we want to
suppress the error in this case. Nor shuld the error be reported
redundantly. We need to *set* the hole flag (see below), but we
don't want to report it. 0==unset. 1==set, 2==set and report.
OGG_ESPAN happens when packet span is indicated but there's no
spanning packet data, or there's spanning packet data and no
declared span. Naturally, this error should also not be
mistriggered due to seek or reset, or reported redundantly. */
static 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 */
ogg_page og; /* only for parsing header values */
long pageno;
oggbyte_buffer ob;
og.header=os->header_tail; /* only for parsing header values */
pageno=ogg_page_pageno(&og);
oggbyte_init(&ob,os->header_tail,0);
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);
/* 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);
}
os->pageno=pageno+1;
os->e_o_s=ogg_page_eos(&og);
os->b_o_s=ogg_page_bos(&og);
}
}
}
/* 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){
int serialno=ogg_page_serialno(og);
int version=ogg_page_version(og);
/* check the serial number */
if(serialno!=os->serialno){
ogg_page_release(og);
return OGG_ESERIAL;
}
if(version>0){
ogg_page_release(og);
return OGG_EVERSION;
}
/* 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);
}
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);
}
memset(og,0,sizeof(*og));
return OGG_SUCCESS;
}
int ogg_stream_reset(ogg_stream_state *os){
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->e_o_s=0;
os->b_o_s=0;
os->pageno=-1;
os->packetno=0;
os->granulepos=0;
os->body_fill=0;
os->lacing_fill=0;
oggbyte_clear(&os->header_build);
os->holeflag=0;
os->spanflag=0;
os->clearflag=0;
os->laceptr=0;
os->body_fill_next=0;
return OGG_SUCCESS;
}
int ogg_stream_reset_serialno(ogg_stream_state *os,int serialno){
ogg_stream_reset(os);
os->serialno=serialno;
return OGG_SUCCESS;
}
static int _packetout(ogg_stream_state *os,ogg_packet *op,int adv){
/* if the incoming packet is a valid reference, release it such that
we don't leak the memory */
ogg_packet_release(op);
/* buffer packets for return */
_span_queued_page(os);
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;
}
}
if(!(os->body_fill&FINFLAG)) return 0;
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,0);
/* 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;
}
/* 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->packetno++;
os->b_o_s=0;
}
return 1;
}
int ogg_stream_packetout(ogg_stream_state *os,ogg_packet *op){
return _packetout(os,op,1);
}
int ogg_stream_packetpeek(ogg_stream_state *os,ogg_packet *op){
return _packetout(os,op,0);
}
int ogg_packet_release(ogg_packet *op) {
if(op){
ogg_buffer_release(op->packet);
memset(op, 0, sizeof(*op));
}
return OGG_SUCCESS;
}
int ogg_page_release(ogg_page *og) {
if(og){
ogg_buffer_release(og->header);
ogg_buffer_release(og->body);
memset(og, 0, sizeof(*og));
}
return OGG_SUCCESS;
}
#ifdef _V_SELFTEST2
#include <stdio.h>
ogg_stream_state *os_en, *os_de;
ogg_sync_state *oy;
ogg_buffer_state *bs;
void checkpacket(ogg_packet *op,int len, int no, int pos){
long j;
static int sequence=0;
static int lastno=0;
oggbyte_buffer ob;
if(ogg_buffer_length(op->packet)!=len){
fprintf(stderr,"incorrect packet length!\n");
exit(1);
}
if(op->granulepos!=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 */
oggbyte_init(&ob,op->packet,0);
for(j=0;j<ogg_buffer_length(op->packet);j++)
if(oggbyte_read1(&ob,j)!=((j+no)&0xff)){
fprintf(stderr,"body data mismatch (1) at pos %ld: %x!=%lx!\n\n",
j,oggbyte_read1(&ob,j),(j+no)&0xff);
exit(1);
}
}
void check_page(unsigned char *data,const int *header,ogg_page *og){
long j;
oggbyte_buffer ob;
/* test buffer lengths */
long header_len=header[26]+27;
long body_len=0;
for(j=27;j<header_len;j++)
body_len+=header[j];
if(header_len!=ogg_buffer_length(og->header)){
fprintf(stderr,"page header length mismatch: %ld correct, buffer is %ld\n",
header_len,ogg_buffer_length(og->header));
exit(1);
}
if(body_len!=ogg_buffer_length(og->body)){
fprintf(stderr,"page body length mismatch: %ld correct, buffer is %ld\n",
body_len,ogg_buffer_length(og->body));
exit(1);
}
/* Test data */
oggbyte_init(&ob,og->body,0);
for(j=0;j<ogg_buffer_length(og->body);j++)
if(oggbyte_read1(&ob,j)!=data[j]){
fprintf(stderr,"body data mismatch (2) at pos %ld: %x!=%x!\n\n",
j,data[j],oggbyte_read1(&ob,j));
exit(1);
}
/* Test header */
oggbyte_init(&ob,og->header,0);
for(j=0;j<ogg_buffer_length(og->header);j++){
if(oggbyte_read1(&ob,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],oggbyte_read1(&ob,j));
fprintf(stderr,"\n");
exit(1);
}
}
if(ogg_buffer_length(og->header)!=header[26]+27){
fprintf(stderr,"header length incorrect! (%ld!=%d)\n",
ogg_buffer_length(og->header),header[26]+27);
exit(1);
}
}
void print_header(ogg_page *og){
int j;
oggbyte_buffer ob;
oggbyte_init(&ob,og->header,0);
fprintf(stderr,"\nHEADER:\n");
fprintf(stderr," capture: %c %c %c %c version: %d flags: %x\n",
oggbyte_read1(&ob,0),oggbyte_read1(&ob,1),
oggbyte_read1(&ob,2),oggbyte_read1(&ob,3),
(int)oggbyte_read1(&ob,4),(int)oggbyte_read1(&ob,5));
fprintf(stderr," granulepos: %08x%08x serialno: %x pageno: %ld\n",
oggbyte_read4(&ob,10),oggbyte_read4(&ob,6),
oggbyte_read4(&ob,14),
(long)oggbyte_read4(&ob,18));
fprintf(stderr," checksum: %08x\n segments: %d (",
oggbyte_read4(&ob,22),(int)oggbyte_read1(&ob,26));
for(j=27;j<ogg_buffer_length(og->header);j++)
fprintf(stderr,"%d ",(int)oggbyte_read1(&ob,j));
fprintf(stderr,")\n\n");
}
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 bufcpy(void *data,ogg_reference *or){
while(or){
memcpy(data,or->buffer->data+or->begin,or->length);
data+=or->length;
or=or->next;
}
}
void bufcpy2(void *data,ogg_reference *or,int begin){
while(or){
if(or->length-begin>0){
memcpy(data,or->buffer->data+or->begin+begin,or->length-begin);
data+=or->length-begin;
}else
begin-=or->length;
or=or->next;
}
}
int bufcmp(void *data,ogg_reference *or){
while(or){
int ret=memcmp(data,or->buffer->data+or->begin,or->length);
if(ret)return ret;
data+=or->length;
or=or->next;
}
return 0;
}
void test_pack(const int *pl, const int **headers){
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 granulepos=7,pageno=0;
int i,j,packets,pageout=0;
int eosflag=0;
int bosflag=0;
for(packets=0;;packets++)if(pl[packets]==-1)break;
for(i=0;i<packets;i++){
/* construct a test packet */
ogg_packet op={0,0,0,0,0,0};
int len=pl[i];
op.packet=ogg_buffer_alloc(bs,len);
op.e_o_s=(pl[i+1]<0?1:0);
op.granulepos=granulepos;
granulepos+=1024;
for(j=0;j<len;j++)data[inptr+j]=i+j;
memcpy(op.packet->buffer->data,data+inptr,len);
op.packet->length=len;
inptr+=j;
/* submit the test packet */
ogg_stream_packetin(os_en,&op);
/* retrieve any finished pages */
{
ogg_page og={0,0,0,0};
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+=ogg_buffer_length(og.body);
pageno++;
/* have a complete page; submit it to sync/decode */
{
ogg_page og_de={0,0,0,0};
ogg_packet op_de={0,0,0,0,0,0},op_de2={0,0,0,0,0,0};
int blen=ogg_buffer_length(og.header)+ogg_buffer_length(og.body);
char *buf=ogg_sync_bufferin(oy,blen);
bufcpy(buf,og.header);
bufcpy(buf+ogg_buffer_length(og.header),og.body);
ogg_sync_wrote(oy,blen);
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+=ogg_buffer_length(og_de.body);
pageout++;
/* submit it to deconstitution */
ogg_stream_pagein(os_de,&og_de);
/* packets out? */
while(ogg_stream_packetpeek(os_de,NULL)>0){
ogg_stream_packetpeek(os_de,&op_de2);
ogg_stream_packetout(os_de,&op_de); /* just catching them all */
/* verify the packets! */
/* check data */
if(bufcmp(data+depacket,op_de.packet)){
fprintf(stderr,"packet data mismatch in decode! pos=%ld\n",
depacket);
exit(1);
}
if(bufcmp(data+depacket,op_de2.packet)){
fprintf(stderr,"packet data mismatch in peek! 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==0 && op_de2.b_o_s==0){
fprintf(stderr,"b_o_s flag not set on peek!\n");
exit(1);
}
if(bosflag && op_de.b_o_s){
fprintf(stderr,"b_o_s flag incorrectly set on packet!\n");
exit(1);
}
if(bosflag && op_de2.b_o_s){
fprintf(stderr,"b_o_s flag incorrectly set on peek!\n");
exit(1);
}
bosflag=1;
depacket+=ogg_buffer_length(op_de.packet);
/* check eos flag */
if(eosflag){
fprintf(stderr,"Multiple decoded packets with eos flag!\n");
exit(1);
}
if(op_de.e_o_s)eosflag=1;
if(op_de.e_o_s!=op_de2.e_o_s){
fprintf(stderr,"packet/peek eosflag mismatch!\n");
exit(1);
}
/* check granulepos flag */
if(op_de.granulepos!=-1){
fprintf(stderr," granule:%ld ",(long)op_de.granulepos);
}
if(op_de.granulepos!=op_de2.granulepos){
fprintf(stderr,"packet/peek granpos mismatch!\n");
exit(1);
}
ogg_packet_release(&op_de);
ogg_packet_release(&op_de2);
}
}
}
ogg_page_release(&og);
}
}
}
_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");
ogg_stream_reset(os_en);
ogg_stream_reset(os_de);
ogg_sync_reset(oy);
}
int main(void){
os_en=ogg_stream_create(0x04030201);
os_de=ogg_stream_create(0x04030201);
oy=ogg_sync_create();
bs=ogg_buffer_create();
/* 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=_ogg_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];
memset(og,0,sizeof(og));
ogg_stream_reset(os_en);
for(i=0;pl[i]!=-1;i++){
ogg_packet op;
int len=pl[i];
op.packet=ogg_buffer_alloc(bs,len);
op.e_o_s=(pl[i+1]<0?1:0);
op.granulepos=(i+1)*1000;
for(j=0;j<len;j++)data[inptr+j]=i+j;
memcpy(op.packet->buffer->data,data+inptr,len);
op.packet->length=len;
ogg_stream_packetin(os_en,&op);
inptr+=j;
}
_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);
}
}
{
ogg_page temp;
if(ogg_stream_pageout(os_en,&temp)>0){
fprintf(stderr,"Too many pages output building sync tests!\n");
exit(1);
}
}
/* Test lost pages on pagein/packetout: no rollback */
{
ogg_page temp={0,0,0,0};
ogg_packet test={0,0,0,0,0,0};
fprintf(stderr,"Testing loss of pages... ");
ogg_sync_reset(oy);
ogg_stream_reset(os_de);
for(i=0;i<5;i++){
bufcpy(ogg_sync_bufferin(oy,ogg_buffer_length(og[i].header)),
og[i].header);
ogg_sync_wrote(oy,ogg_buffer_length(og[i].header));
bufcpy(ogg_sync_bufferin(oy,ogg_buffer_length(og[i].body)),
og[i].body);
ogg_sync_wrote(oy,ogg_buffer_length(og[i].body));
}
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_page_release(&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);
ogg_packet_release(&test);
if(ogg_stream_packetout(os_de,&test)!=1)error();
checkpacket(&test,100,1,-1);
ogg_packet_release(&test);
if(ogg_stream_packetout(os_de,&test)!=1)error();
checkpacket(&test,4079,2,3000);
ogg_packet_release(&test);
if(ogg_stream_packetout(os_de,&test)!=OGG_HOLE){
fprintf(stderr,"Error: loss of page did not return error\n");
exit(1);
}
ogg_packet_release(&test);
if(ogg_stream_packetout(os_de,&test)!=1)error();
checkpacket(&test,76,5,-1);
ogg_packet_release(&test);
if(ogg_stream_packetout(os_de,&test)!=1)error();
checkpacket(&test,34,6,-1);
ogg_packet_release(&test);
fprintf(stderr,"ok.\n");
}
/* Test lost pages on pagein/packetout: rollback with continuation */
{
ogg_page temp={0,0,0,0};
ogg_packet test={0,0,0,0,0,0};
fprintf(stderr,"Testing loss of pages (rollback required)... ");
ogg_sync_reset(oy);
ogg_stream_reset(os_de);
for(i=0;i<5;i++){
bufcpy(ogg_sync_bufferin(oy,ogg_buffer_length(og[i].header)),
og[i].header);
ogg_sync_wrote(oy,ogg_buffer_length(og[i].header));
bufcpy(ogg_sync_bufferin(oy,ogg_buffer_length(og[i].body)),
og[i].body);
ogg_sync_wrote(oy,ogg_buffer_length(og[i].body));
}
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);
ogg_page_release(&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);
ogg_packet_release(&test);
if(ogg_stream_packetout(os_de,&test)!=1)error();
checkpacket(&test,100,1,-1);
ogg_packet_release(&test);
if(ogg_stream_packetout(os_de,&test)!=1)error();
checkpacket(&test,4079,2,3000);
ogg_packet_release(&test);
if(ogg_stream_packetout(os_de,&test)!=1)error();
checkpacket(&test,2956,3,4000);
ogg_packet_release(&test);
if(ogg_stream_packetout(os_de,&test)!=OGG_HOLE){
fprintf(stderr,"Error: loss of page did not return error\n");
exit(1);
}
ogg_packet_release(&test);
if(ogg_stream_packetout(os_de,&test)!=1)error();
checkpacket(&test,300,13,14000);
ogg_packet_release(&test);
fprintf(stderr,"ok.\n");
}
/* the rest only test sync */
{
ogg_page og_de={0,0,0,0};
/* Test fractional page inputs: incomplete capture */
fprintf(stderr,"Testing sync on partial inputs... ");
ogg_sync_reset(oy);
bufcpy(ogg_sync_bufferin(oy,ogg_buffer_length(og[1].header)),og[1].header);
ogg_sync_wrote(oy,3);
if(ogg_sync_pageout(oy,&og_de)>0)error();
ogg_page_release(&og_de);
/* Test fractional page inputs: incomplete fixed header */
bufcpy2(ogg_sync_bufferin(oy,ogg_buffer_length(og[1].header)),og[1].header,3);
ogg_sync_wrote(oy,20);
if(ogg_sync_pageout(oy,&og_de)>0)error();
ogg_page_release(&og_de);
/* Test fractional page inputs: incomplete header */
bufcpy2(ogg_sync_bufferin(oy,ogg_buffer_length(og[1].header)),og[1].header,23);
ogg_sync_wrote(oy,5);
if(ogg_sync_pageout(oy,&og_de)>0)error();
ogg_page_release(&og_de);
/* Test fractional page inputs: incomplete body */
bufcpy2(ogg_sync_bufferin(oy,ogg_buffer_length(og[1].header)),og[1].header,28);
ogg_sync_wrote(oy,ogg_buffer_length(og[1].header)-28);
if(ogg_sync_pageout(oy,&og_de)>0)error();
ogg_page_release(&og_de);
bufcpy(ogg_sync_bufferin(oy,ogg_buffer_length(og[1].body)),og[1].body);
ogg_sync_wrote(oy,1000);
if(ogg_sync_pageout(oy,&og_de)>0)error();
ogg_page_release(&og_de);
bufcpy2(ogg_sync_bufferin(oy,ogg_buffer_length(og[1].body)),og[1].body,1000);
ogg_sync_wrote(oy,ogg_buffer_length(og[1].body)-1000);
if(ogg_sync_pageout(oy,&og_de)<=0)error();
ogg_page_release(&og_de);
fprintf(stderr,"ok.\n");
}
/* Test fractional page inputs: page + incomplete capture */
{
ogg_page og_de={0,0,0,0};
fprintf(stderr,"Testing sync on 1+partial inputs... ");
ogg_sync_reset(oy);
bufcpy(ogg_sync_bufferin(oy,ogg_buffer_length(og[1].header)),og[1].header);
ogg_sync_wrote(oy,ogg_buffer_length(og[1].header));
bufcpy(ogg_sync_bufferin(oy,ogg_buffer_length(og[1].body)),og[1].body);
ogg_sync_wrote(oy,ogg_buffer_length(og[1].body));
bufcpy(ogg_sync_bufferin(oy,ogg_buffer_length(og[1].header)),og[1].header);
ogg_sync_wrote(oy,20);
if(ogg_sync_pageout(oy,&og_de)<=0)error();
ogg_page_release(&og_de);
if(ogg_sync_pageout(oy,&og_de)>0)error();
ogg_page_release(&og_de);
bufcpy2(ogg_sync_bufferin(oy,ogg_buffer_length(og[1].header)),og[1].header,20);
ogg_sync_wrote(oy,ogg_buffer_length(og[1].header)-20);
bufcpy(ogg_sync_bufferin(oy,ogg_buffer_length(og[1].body)),og[1].body);
ogg_sync_wrote(oy,ogg_buffer_length(og[1].body));
if(ogg_sync_pageout(oy,&og_de)<=0)error();
ogg_page_release(&og_de);
fprintf(stderr,"ok.\n");
}
/* Test recapture: garbage + page */
{
ogg_page og_de={0,0,0,0};
fprintf(stderr,"Testing search for capture... ");
ogg_sync_reset(oy);
/* 'garbage' */
bufcpy(ogg_sync_bufferin(oy,ogg_buffer_length(og[1].body)),og[1].body);
ogg_sync_wrote(oy,ogg_buffer_length(og[1].body));
bufcpy(ogg_sync_bufferin(oy,ogg_buffer_length(og[1].header)),og[1].header);
ogg_sync_wrote(oy,ogg_buffer_length(og[1].header));
bufcpy(ogg_sync_bufferin(oy,ogg_buffer_length(og[1].body)),og[1].body);
ogg_sync_wrote(oy,ogg_buffer_length(og[1].body));
bufcpy(ogg_sync_bufferin(oy,ogg_buffer_length(og[2].header)),og[2].header);
ogg_sync_wrote(oy,20);
if(ogg_sync_pageout(oy,&og_de)>0)error();
ogg_page_release(&og_de);
if(ogg_sync_pageout(oy,&og_de)<=0)error();
ogg_page_release(&og_de);
if(ogg_sync_pageout(oy,&og_de)>0)error();
ogg_page_release(&og_de);
bufcpy2(ogg_sync_bufferin(oy,ogg_buffer_length(og[2].header)),og[2].header,20);
ogg_sync_wrote(oy,ogg_buffer_length(og[2].header)-20);
bufcpy(ogg_sync_bufferin(oy,ogg_buffer_length(og[2].body)),og[2].body);
ogg_sync_wrote(oy,ogg_buffer_length(og[2].body));
if(ogg_sync_pageout(oy,&og_de)<=0)error();
ogg_page_release(&og_de);
fprintf(stderr,"ok.\n");
}
/* Test recapture: page + garbage + page */
{
ogg_page og_de={0,0,0,0};
fprintf(stderr,"Testing recapture... ");
ogg_sync_reset(oy);
bufcpy(ogg_sync_bufferin(oy,ogg_buffer_length(og[1].header)),og[1].header);
ogg_sync_wrote(oy,ogg_buffer_length(og[1].header));
bufcpy(ogg_sync_bufferin(oy,ogg_buffer_length(og[1].body)),og[1].body);
ogg_sync_wrote(oy,ogg_buffer_length(og[1].body));
/* garbage */
bufcpy(ogg_sync_bufferin(oy,ogg_buffer_length(og[2].header)),og[2].header);
ogg_sync_wrote(oy,ogg_buffer_length(og[2].header));
bufcpy(ogg_sync_bufferin(oy,ogg_buffer_length(og[2].header)),og[2].header);
ogg_sync_wrote(oy,ogg_buffer_length(og[2].header));
if(ogg_sync_pageout(oy,&og_de)<=0)error();
ogg_page_release(&og_de);
bufcpy(ogg_sync_bufferin(oy,ogg_buffer_length(og[2].body)),og[2].body);
ogg_sync_wrote(oy,ogg_buffer_length(og[2].body)-5);
bufcpy(ogg_sync_bufferin(oy,ogg_buffer_length(og[3].header)),og[3].header);
ogg_sync_wrote(oy,ogg_buffer_length(og[3].header));
bufcpy(ogg_sync_bufferin(oy,ogg_buffer_length(og[3].body)),og[3].body);
ogg_sync_wrote(oy,ogg_buffer_length(og[3].body));
if(ogg_sync_pageout(oy,&og_de)>0)error();
ogg_page_release(&og_de);
if(ogg_sync_pageout(oy,&og_de)<=0)error();
ogg_page_release(&og_de);
fprintf(stderr,"ok.\n");
}
ogg_stream_destroy(os_en);
for(i=0;i<5;i++)
ogg_page_release(&og[i]);
}
ogg_stream_destroy(os_de);
ogg_sync_destroy(oy);
ogg_buffer_destroy(bs);
return 0;
}
#endif