582 lines
14 KiB
C#
Raw Normal View History

/* csogg
* Copyright (C) 2000 ymnk, JCraft,Inc.
*
* Written by: 2000 ymnk<ymnk@jcraft.com>
* Ported to C# from JOrbis by: Mark Crichton <crichton@gimp.org>
*
* Thanks go to the JOrbis team, for licencing the code under the
* LGPL, making my job a lot easier.
*
* 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.
*/
using System;
using System.Text;
namespace csogg
{
/// <summary>
/// Summary description for StreamState.
/// </summary>
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;
StreamState(int serialno) : this()
{
init(serialno);
}
public StreamState()
{
init();
}
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];
Array.Copy(body_data, 0, foo, 0, body_data.Length);
body_data=foo;
}
}
void lacing_expand(int needed)
{
if(lacing_storage<=lacing_fill+needed)
{
lacing_storage+=(needed+32);
int[] foo=new int[lacing_storage];
Array.Copy(lacing_vals, 0, foo, 0, lacing_vals.Length);
lacing_vals=foo;
long[] bar=new long[lacing_storage];
Array.Copy(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)
{
Array.Copy(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 */
Array.Copy(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)
{
Array.Copy(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)
{
Array.Copy(lacing_vals, lr, lacing_vals, 0, lacing_fill-lr);
Array.Copy(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);
Array.Copy(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 */
String oggs_str = "OggS";
Encoding AE = Encoding.UTF8;
byte[] oggs_byt = AE.GetBytes(oggs_str);
Array.Copy(oggs_byt, 0, header, 0, oggs_byt.Length);
/* 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 */
lacing_fill-=vals;
Array.Copy(lacing_vals, vals, lacing_vals, 0, lacing_fill*4);
Array.Copy(granule_vals, vals, granule_vals, 0, lacing_fill*8);
body_returned+=bytes;
/* 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((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);
}
}
}