/* JOrbis * Copyright (C) 2000 ymnk, JCraft,Inc. * * Written by: 2000 ymnk<ymnk@jcraft.com> * * Many thanks to * Monty <monty@xiph.org> and * The XIPHOPHORUS Company http://www.xiph.org/ . * JOrbis has been based on their awesome works, Vorbis codec. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License * as published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package com.jcraft.jogg; public class StreamState{ byte[] body_data; /* bytes from packet bodies */ int body_storage; /* storage elements allocated */ int body_fill; /* elements stored; fill mark */ private int body_returned; /* elements of fill returned */ int[] lacing_vals; /* The values that will go to the segment table */ long[] granule_vals; /* pcm_pos values for headers. Not compact this way, but it is simple coupled to the lacing fifo */ int lacing_storage; int lacing_fill; int lacing_packet; int lacing_returned; byte[] header=new byte[282]; /* working space for header encode */ int header_fill; public int e_o_s; /* set when we have buffered the last packet in the logical bitstream */ int b_o_s; /* set after we've written the initial page of a logical bitstream */ int serialno; int pageno; long packetno; /* sequence number for decode; the framing knows where there's a hole in the data, but we need coupling so that the codec (which is in a seperate abstraction layer) also knows about the gap */ long granulepos; public StreamState(){ init(); } StreamState(int serialno){ this(); init(serialno); } void init(){ body_storage=16*1024; body_data=new byte[body_storage]; lacing_storage=1024; lacing_vals=new int[lacing_storage]; granule_vals=new long[lacing_storage]; } public void init(int serialno){ if(body_data==null){ init(); } else{ for(int i=0; i<body_data.length; i++) body_data[i]=0; for(int i=0; i<lacing_vals.length; i++) lacing_vals[i]=0; for(int i=0; i<granule_vals.length; i++) granule_vals[i]=0; } this.serialno=serialno; } public void clear(){ body_data=null; lacing_vals=null; granule_vals=null; //memset(os,0,sizeof(ogg_stream_state)); } void destroy(){ clear(); } void body_expand(int needed){ if(body_storage<=body_fill+needed){ body_storage+=(needed+1024); byte[] foo=new byte[body_storage]; System.arraycopy(body_data, 0, foo, 0, body_data.length); body_data=foo; //System.out.println("expand: body_fill="+body_fill+", body_storage="+body_data.length); } } void lacing_expand(int needed){ if(lacing_storage<=lacing_fill+needed){ lacing_storage+=(needed+32); int[] foo=new int[lacing_storage]; System.arraycopy(lacing_vals, 0, foo, 0, lacing_vals.length); lacing_vals=foo; long[] bar=new long[lacing_storage]; System.arraycopy(granule_vals, 0, bar, 0, granule_vals.length); granule_vals=bar; } } /* submit data to the internal buffer of the framing engine */ public int packetin(Packet op){ int lacing_val=op.bytes/255+1; if(body_returned!=0){ /* advance packet data according to the body_returned pointer. We had to keep it around to return a pointer into the buffer last call */ body_fill-=body_returned; if(body_fill!=0){ // memmove(os->body_data,os->body_data+os->body_returned, // os->body_fill*sizeof(char)); System.arraycopy(body_data, body_returned, body_data, 0, body_fill); } body_returned=0; } /* make sure we have the buffer storage */ body_expand(op.bytes); lacing_expand(lacing_val); /* 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 */ System.arraycopy(op.packet_base, op.packet, body_data, body_fill, op.bytes); body_fill+=op.bytes; //System.out.println("add: "+body_fill); /* Store lacing vals for this packet */ int j; for(j=0;j<lacing_val-1;j++){ lacing_vals[lacing_fill+j]=255; granule_vals[lacing_fill+j]=granulepos; } lacing_vals[lacing_fill+j]=(op.bytes)%255; granulepos=granule_vals[lacing_fill+j]=op.granulepos; /* flag the first segment as the beginning of the packet */ lacing_vals[lacing_fill]|= 0x100; lacing_fill+=lacing_val; /* for the sake of completeness */ packetno++; if(op.e_o_s!=0)e_o_s=1; return(0); } public int packetout(Packet op){ /* The last part of decode. We have the stream broken into packet segments. Now we need to group them into packets (or return the out of sync markers) */ int ptr=lacing_returned; if(lacing_packet<=ptr){ return(0); } if((lacing_vals[ptr]&0x400)!=0){ /* We lost sync here; let the app know */ lacing_returned++; /* we need to tell the codec there's a gap; it might need to handle previous packet dependencies. */ packetno++; return(-1); } /* Gather the whole packet. We'll have no holes or a partial packet */ { int size=lacing_vals[ptr]&0xff; int bytes=0; op.packet_base=body_data; op.packet=body_returned; op.e_o_s=lacing_vals[ptr]&0x200; /* last packet of the stream? */ op.b_o_s=lacing_vals[ptr]&0x100; /* first packet of the stream? */ bytes+=size; while(size==255){ int val=lacing_vals[++ptr]; size=val&0xff; if((val&0x200)!=0)op.e_o_s=0x200; bytes+=size; } op.packetno=packetno; op.granulepos=granule_vals[ptr]; op.bytes=bytes; //System.out.println(this+" # body_returned="+body_returned); body_returned+=bytes; //System.out.println(this+"## body_returned="+body_returned); lacing_returned=ptr+1; } packetno++; return(1); } // add the incoming page to the stream state; we decompose the page // into packet segments here as well. public int pagein(Page og){ byte[] header_base=og.header_base; int header=og.header; byte[] body_base=og.body_base; int body=og.body; int bodysize=og.body_len; int segptr=0; int version=og.version(); int continued=og.continued(); int bos=og.bos(); int eos=og.eos(); long granulepos=og.granulepos(); int _serialno=og.serialno(); int _pageno=og.pageno(); int segments=header_base[header+26]&0xff; // clean up 'returned data' { int lr=lacing_returned; int br=body_returned; // body data //System.out.println("br="+br+", body_fill="+body_fill); if(br!=0){ body_fill-=br; if(body_fill!=0){ System.arraycopy(body_data, br, body_data, 0, body_fill); } body_returned=0; } //System.out.println("?? br="+br+", body_fill="+body_fill+" body_returned="+body_returned); if(lr!=0){ // segment table if((lacing_fill-lr)!=0){ System.arraycopy(lacing_vals, lr, lacing_vals, 0, lacing_fill-lr); System.arraycopy(granule_vals, lr, granule_vals, 0, lacing_fill-lr); } lacing_fill-=lr; lacing_packet-=lr; lacing_returned=0; } } // check the serial number if(_serialno!=serialno)return(-1); if(version>0)return(-1); lacing_expand(segments+1); // are we in sequence? if(_pageno!=pageno){ int i; // unroll previous partial packet (if any) for(i=lacing_packet;i<lacing_fill;i++){ body_fill-=lacing_vals[i]&0xff; //System.out.println("??"); } lacing_fill=lacing_packet; // make a note of dropped data in segment table if(pageno!=-1){ lacing_vals[lacing_fill++]=0x400; lacing_packet++; } // are we a 'continued packet' page? If so, we'll need to skip // some segments if(continued!=0){ bos=0; for(;segptr<segments;segptr++){ int val=(header_base[header+27+segptr]&0xff); body+=val; bodysize-=val; if(val<255){ segptr++; break; } } } } //System.out.println("bodysize="+bodysize); if(bodysize!=0){ body_expand(bodysize); System.arraycopy(body_base, body, body_data, body_fill, bodysize); body_fill+=bodysize; } //System.out.println("bodyfill="+body_fill); { int saved=-1; while(segptr<segments){ int val=(header_base[header+27+segptr]&0xff); lacing_vals[lacing_fill]=val; granule_vals[lacing_fill]=-1; if(bos!=0){ lacing_vals[lacing_fill]|=0x100; bos=0; } if(val<255)saved=lacing_fill; lacing_fill++; segptr++; if(val<255)lacing_packet=lacing_fill; } /* set the granulepos on the last pcmval of the last full packet */ if(saved!=-1){ granule_vals[saved]=granulepos; } } if(eos!=0){ e_o_s=1; if(lacing_fill>0) lacing_vals[lacing_fill-1]|=0x200; } pageno=_pageno+1; return(0); } /* This will flush remaining packets into a page (returning nonzero), even if there is not enough data to trigger a flush normally (undersized page). If there are no packets or partial packets to flush, ogg_stream_flush returns 0. Note that ogg_stream_flush will try to flush a normal sized page like ogg_stream_pageout; a call to ogg_stream_flush does not gurantee that all packets have flushed. Only a return value of 0 from ogg_stream_flush indicates all packet data is flushed into pages. ogg_stream_page will flush the last page in a stream even if it's undersized; you almost certainly want to use ogg_stream_pageout (and *not* ogg_stream_flush) unless you need to flush an undersized page in the middle of a stream for some reason. */ public int flush(Page og){ //System.out.println(this+" ---body_returned: "+body_returned); int i; int vals=0; int maxvals=(lacing_fill>255?255:lacing_fill); int bytes=0; int acc=0; long granule_pos=granule_vals[0]; if(maxvals==0)return(0); /* construct a page */ /* decide how many segments to include */ /* If this is the initial header case, the first page must only include the initial header packet */ if(b_o_s==0){ /* 'initial header page' case */ granule_pos=0; for(vals=0;vals<maxvals;vals++){ if((lacing_vals[vals]&0x0ff)<255){ vals++; break; } } } else{ for(vals=0;vals<maxvals;vals++){ if(acc>4096)break; acc+=(lacing_vals[vals]&0x0ff); granule_pos=granule_vals[vals]; } } /* construct the header in temp storage */ System.arraycopy("OggS".getBytes(), 0, header, 0, 4); /* stream structure version */ header[4]=0x00; /* continued packet flag? */ header[5]=0x00; if((lacing_vals[0]&0x100)==0)header[5]|=0x01; /* first page flag? */ if(b_o_s==0) header[5]|=0x02; /* last page flag? */ if(e_o_s!=0 && lacing_fill==vals) header[5]|=0x04; b_o_s=1; /* 64 bits of PCM position */ for(i=6;i<14;i++){ header[i]=(byte)granule_pos; granule_pos>>>=8; } /* 32 bits of stream serial number */ { int _serialno=serialno; for(i=14;i<18;i++){ header[i]=(byte)_serialno; _serialno>>>=8; } } /* 32 bits of page counter (we have both counter and page header because this val can roll over) */ if(pageno==-1)pageno=0; /* because someone called stream_reset; this would be a strange thing to do in an encode stream, but it has plausible uses */ { int _pageno=pageno++; for(i=18;i<22;i++){ header[i]=(byte)_pageno; _pageno>>>=8; } } /* zero for computation; filled in later */ header[22]=0; header[23]=0; header[24]=0; header[25]=0; /* segment table */ header[26]=(byte)vals; for(i=0;i<vals;i++){ header[i+27]=(byte)lacing_vals[i]; bytes+=(header[i+27]&0xff); } /* set pointers in the ogg_page struct */ og.header_base=header; og.header=0; og.header_len=header_fill=vals+27; og.body_base=body_data; og.body=body_returned; og.body_len=bytes; /* advance the lacing data and set the body_returned pointer */ //System.out.println("###body_returned: "+body_returned); lacing_fill-=vals; System.arraycopy(lacing_vals, vals, lacing_vals, 0, lacing_fill*4); System.arraycopy(granule_vals, vals, granule_vals, 0, lacing_fill*8); body_returned+=bytes; //System.out.println("####body_returned: "+body_returned); /* calculate the checksum */ og.checksum(); /* done */ return(1); } /* This constructs pages from buffered packet segments. The pointers returned are to static buffers; do not free. The returned buffers are good only until the next call (using the same ogg_stream_state) */ public int pageout(Page og){ // if(body_returned!=0){ // /* advance packet data according to the body_returned pointer. We // had to keep it around to return a pointer into the buffer last // call */ // // body_fill-=body_returned; // if(body_fill!=0){ // overlap? // System.arraycopy(body_data, body_returned, body_data, 0, body_fill); // } // body_returned=0; // } // //System.out.println("pageout: e_o_s="+e_o_s+" lacing_fill="+lacing_fill+" body_fill="+body_fill+", lacing_fill="+lacing_fill+" b_o_s="+b_o_s); // // if((e_o_s!=0&&lacing_fill!=0) || /* 'were done, now flush' case */ // body_fill > 4096 || /* 'page nominal size' case */ // lacing_fill>=255 || /* 'segment table full' case */ // (lacing_fill!=0&&b_o_s==0)){ /* 'initial header page' case */ // int vals=0,bytes=0; // int maxvals=(lacing_fill>255?255:lacing_fill); // long acc=0; // long pcm_pos=granule_vals[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(b_o_s==0){ /* 'initial header page' case */ // pcm_pos=0; // for(vals=0;vals<maxvals;vals++){ // if((lacing_vals[vals]&0x0ff)<255){ // vals++; // break; // } // } // } // else{ // for(vals=0;vals<maxvals;vals++){ // if(acc>4096)break; // acc+=lacing_vals[vals]&0x0ff; // pcm_pos=granule_vals[vals]; // } // } // // /* construct the header in temp storage */ // System.arraycopy("OggS".getBytes(), 0, header, 0, 4); // // /* stream structure version */ // header[4]=0x00; // // /* continued packet flag? */ // header[5]=0x00; // if((lacing_vals[0]&0x100)==0)header[5]|=0x01; // /* first page flag? */ // if(b_o_s==0)header[5]|=0x02; // /* last page flag? */ // if(e_o_s!=0 && lacing_fill==vals)header[5]|=0x04; // b_o_s=1; // // /* 64 bits of PCM position */ // for(int i=6;i<14;i++){ // header[i]=(byte)pcm_pos; // pcm_pos>>>=8; // } // // /* 32 bits of stream serial number */ // { // int serialn=serialno; // for(int i=14;i<18;i++){ // header[i]=(byte)serialn; // serialn>>>=8; // } // } // // ///* 32 bits of page counter (we have both counter and page header // because this val can roll over) */ // if(pageno==-1)pageno=0; /* because someone called // stream_reset; this would be a // strange thing to do in an // encode stream, but it has // plausible uses */ // { // int pagen=pageno++; // for(int i=18;i<22;i++){ // header[i]=(byte)pagen; // pagen>>>=8; // } // } // // /* zero for computation; filled in later */ // header[22]=0; // header[23]=0; // header[24]=0; // header[25]=0; // // /* segment table */ // header[26]=(byte)vals; // for(int i=0;i<vals;i++){ // header[i+27]=(byte)lacing_vals[i]; // bytes+=header[i+27]&0xff; //// bytes+=header[i+27]=(lacing_vals[i]&0xff); // } // // /* advance the lacing data and set the body_returned pointer */ // // lacing_fill-=vals; // System.arraycopy(lacing_vals, vals, lacing_vals, 0, lacing_fill); // System.arraycopy(granule_vals, vals, granule_vals, 0, lacing_fill); // body_returned=bytes; // // /* set pointers in the ogg_page struct */ // og.header_base=header; // og.header=0; // og.header_len=header_fill=vals+27; // // og.body_base=body_data; // og.body=0; // og.body_len=bytes; // // /* calculate the checksum */ // // og.checksum(); // return(1); // } // /* not enough data to construct a page and not end of stream */ // return(0); //System.out.println("pageout: "+body_returned); if((e_o_s!=0&&lacing_fill!=0) || /* 'were done, now flush' case */ body_fill-body_returned> 4096 || /* 'page nominal size' case */ lacing_fill>=255 || /* 'segment table full' case */ (lacing_fill!=0&&b_o_s==0)){ /* 'initial header page' case */ return flush(og); } return 0; } public int eof(){ return e_o_s; } public int reset(){ body_fill=0; body_returned=0; lacing_fill=0; lacing_packet=0; lacing_returned=0; header_fill=0; e_o_s=0; b_o_s=0; pageno=-1; packetno=0; granulepos=0; return(0); } }