543 lines
14 KiB
C#
543 lines
14 KiB
C#
|
/* csvorbis
|
||
|
* 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 csogg;
|
||
|
|
||
|
namespace csvorbis
|
||
|
{
|
||
|
class StaticCodeBook
|
||
|
{
|
||
|
internal int dim; // codebook dimensions (elements per vector)
|
||
|
internal int entries; // codebook entries
|
||
|
internal int[] lengthlist; // codeword lengths in bits
|
||
|
|
||
|
// mapping
|
||
|
internal int maptype; // 0=none
|
||
|
// 1=implicitly populated values from map column
|
||
|
// 2=listed arbitrary values
|
||
|
|
||
|
// The below does a linear, single monotonic sequence mapping.
|
||
|
internal int q_min; // packed 32 bit float; quant value 0 maps to minval
|
||
|
internal int q_delta; // packed 32 bit float; val 1 - val 0 == delta
|
||
|
internal int q_quant; // bits: 0 < quant <= 16
|
||
|
internal int q_sequencep; // bitflag
|
||
|
|
||
|
// additional information for log (dB) mapping; the linear mapping
|
||
|
// is assumed to actually be values in dB. encodebias is used to
|
||
|
// assign an error weight to 0 dB. We have two additional flags:
|
||
|
// zeroflag indicates if entry zero is to represent -Inf dB; negflag
|
||
|
// indicates if we're to represent negative linear values in a
|
||
|
// mirror of the positive mapping.
|
||
|
|
||
|
internal int[] quantlist; // map == 1: (int)(entries/dim) element column map
|
||
|
// map == 2: list of dim*entries quantized entry vals
|
||
|
|
||
|
// encode helpers
|
||
|
internal EncodeAuxNearestMatch nearest_tree;
|
||
|
internal EncodeAuxThreshMatch thresh_tree;
|
||
|
|
||
|
internal StaticCodeBook(){}
|
||
|
internal StaticCodeBook(int dim, int entries, int[] lengthlist,
|
||
|
int maptype, int q_min, int q_delta,
|
||
|
int q_quant, int q_sequencep, int[] quantlist,
|
||
|
//EncodeAuxNearestmatch nearest_tree,
|
||
|
Object nearest_tree,
|
||
|
// EncodeAuxThreshmatch thresh_tree,
|
||
|
Object thresh_tree
|
||
|
) : this()
|
||
|
{
|
||
|
this.dim=dim; this.entries=entries; this.lengthlist=lengthlist;
|
||
|
this.maptype=maptype; this.q_min=q_min; this.q_delta=q_delta;
|
||
|
this.q_quant=q_quant; this.q_sequencep=q_sequencep;
|
||
|
this.quantlist=quantlist;
|
||
|
}
|
||
|
|
||
|
internal int pack(csBuffer opb)
|
||
|
{
|
||
|
int i;
|
||
|
bool ordered=false;
|
||
|
|
||
|
opb.write(0x564342,24);
|
||
|
opb.write(dim, 16);
|
||
|
opb.write(entries, 24);
|
||
|
|
||
|
// pack the codewords. There are two packings; length ordered and
|
||
|
// length random. Decide between the two now.
|
||
|
|
||
|
for(i=1;i<entries;i++)
|
||
|
{
|
||
|
if(lengthlist[i]<lengthlist[i-1])break;
|
||
|
}
|
||
|
if(i==entries)ordered=true;
|
||
|
|
||
|
if(ordered)
|
||
|
{
|
||
|
// length ordered. We only need to say how many codewords of
|
||
|
// each length. The actual codewords are generated
|
||
|
// deterministically
|
||
|
|
||
|
int count=0;
|
||
|
opb.write(1,1); // ordered
|
||
|
opb.write(lengthlist[0]-1,5); // 1 to 32
|
||
|
|
||
|
for(i=1;i<entries;i++)
|
||
|
{
|
||
|
int _this=lengthlist[i];
|
||
|
int _last=lengthlist[i-1];
|
||
|
if(_this>_last)
|
||
|
{
|
||
|
for(int j=_last;j<_this;j++)
|
||
|
{
|
||
|
opb.write(i-count,ilog(entries-count));
|
||
|
count=i;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
opb.write(i-count,ilog(entries-count));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// length random. Again, we don't code the codeword itself, just
|
||
|
// the length. This time, though, we have to encode each length
|
||
|
opb.write(0,1); // unordered
|
||
|
|
||
|
// algortihmic mapping has use for 'unused entries', which we tag
|
||
|
// here. The algorithmic mapping happens as usual, but the unused
|
||
|
// entry has no codeword.
|
||
|
for(i=0;i<entries;i++)
|
||
|
{
|
||
|
if(lengthlist[i]==0)break;
|
||
|
}
|
||
|
|
||
|
if(i==entries)
|
||
|
{
|
||
|
opb.write(0,1); // no unused entries
|
||
|
for(i=0;i<entries;i++)
|
||
|
{
|
||
|
opb.write(lengthlist[i]-1,5);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
opb.write(1,1); // we have unused entries; thus we tag
|
||
|
for(i=0;i<entries;i++)
|
||
|
{
|
||
|
if(lengthlist[i]==0)
|
||
|
{
|
||
|
opb.write(0,1);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
opb.write(1,1);
|
||
|
opb.write(lengthlist[i]-1,5);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// is the entry number the desired return value, or do we have a
|
||
|
// mapping? If we have a mapping, what type?
|
||
|
opb.write(maptype,4);
|
||
|
switch(maptype)
|
||
|
{
|
||
|
case 0:
|
||
|
// no mapping
|
||
|
break;
|
||
|
case 1:
|
||
|
case 2:
|
||
|
// implicitly populated value mapping
|
||
|
// explicitly populated value mapping
|
||
|
if(quantlist==null)
|
||
|
{
|
||
|
// no quantlist? error
|
||
|
return(-1);
|
||
|
}
|
||
|
|
||
|
// values that define the dequantization
|
||
|
opb.write(q_min,32);
|
||
|
opb.write(q_delta,32);
|
||
|
opb.write(q_quant-1,4);
|
||
|
opb.write(q_sequencep,1);
|
||
|
|
||
|
{
|
||
|
int quantvals=0;
|
||
|
switch(maptype)
|
||
|
{
|
||
|
case 1:
|
||
|
// a single column of (c->entries/c->dim) quantized values for
|
||
|
// building a full value list algorithmically (square lattice)
|
||
|
quantvals=maptype1_quantvals();
|
||
|
break;
|
||
|
case 2:
|
||
|
// every value (c->entries*c->dim total) specified explicitly
|
||
|
quantvals=entries*dim;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// quantized values
|
||
|
for(i=0;i<quantvals;i++)
|
||
|
{
|
||
|
opb.write(Math.Abs(quantlist[i]),q_quant);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
// error case; we don't have any other map types now
|
||
|
return(-1);
|
||
|
}
|
||
|
return(0);
|
||
|
}
|
||
|
/*
|
||
|
*/
|
||
|
|
||
|
// unpacks a codebook from the packet buffer into the codebook struct,
|
||
|
// readies the codebook auxiliary structures for decode
|
||
|
internal int unpack(csBuffer opb)
|
||
|
{
|
||
|
int i;
|
||
|
//memset(s,0,sizeof(static_codebook));
|
||
|
|
||
|
// make sure alignment is correct
|
||
|
if(opb.read(24)!=0x564342)
|
||
|
{
|
||
|
// goto _eofout;
|
||
|
clear();
|
||
|
return(-1);
|
||
|
}
|
||
|
|
||
|
// first the basic parameters
|
||
|
dim=opb.read(16);
|
||
|
entries=opb.read(24);
|
||
|
if(entries==-1)
|
||
|
{
|
||
|
// goto _eofout;
|
||
|
clear();
|
||
|
return(-1);
|
||
|
}
|
||
|
|
||
|
// codeword ordering.... length ordered or unordered?
|
||
|
switch(opb.read(1))
|
||
|
{
|
||
|
case 0:
|
||
|
// unordered
|
||
|
lengthlist=new int[entries];
|
||
|
|
||
|
// allocated but unused entries?
|
||
|
if(opb.read(1)!=0)
|
||
|
{
|
||
|
// yes, unused entries
|
||
|
|
||
|
for(i=0;i<entries;i++)
|
||
|
{
|
||
|
if(opb.read(1)!=0)
|
||
|
{
|
||
|
int num=opb.read(5);
|
||
|
if(num==-1)
|
||
|
{
|
||
|
// goto _eofout;
|
||
|
clear();
|
||
|
return(-1);
|
||
|
}
|
||
|
lengthlist[i]=num+1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
lengthlist[i]=0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// all entries used; no tagging
|
||
|
for(i=0;i<entries;i++)
|
||
|
{
|
||
|
int num=opb.read(5);
|
||
|
if(num==-1)
|
||
|
{
|
||
|
// goto _eofout;
|
||
|
clear();
|
||
|
return(-1);
|
||
|
}
|
||
|
lengthlist[i]=num+1;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case 1:
|
||
|
// ordered
|
||
|
{
|
||
|
int length=opb.read(5)+1;
|
||
|
lengthlist=new int[entries];
|
||
|
|
||
|
for(i=0;i<entries;)
|
||
|
{
|
||
|
int num=opb.read(ilog(entries-i));
|
||
|
if(num==-1)
|
||
|
{
|
||
|
// goto _eofout;
|
||
|
clear();
|
||
|
return(-1);
|
||
|
}
|
||
|
for(int j=0;j<num;j++,i++)
|
||
|
{
|
||
|
lengthlist[i]=length;
|
||
|
}
|
||
|
length++;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
// EOF
|
||
|
return(-1);
|
||
|
}
|
||
|
|
||
|
// Do we have a mapping to unpack?
|
||
|
switch((maptype=opb.read(4)))
|
||
|
{
|
||
|
case 0:
|
||
|
// no mapping
|
||
|
break;
|
||
|
case 1:
|
||
|
case 2:
|
||
|
// implicitly populated value mapping
|
||
|
// explicitly populated value mapping
|
||
|
q_min=opb.read(32);
|
||
|
q_delta=opb.read(32);
|
||
|
q_quant=opb.read(4)+1;
|
||
|
q_sequencep=opb.read(1);
|
||
|
|
||
|
{
|
||
|
int quantvals=0;
|
||
|
switch(maptype)
|
||
|
{
|
||
|
case 1:
|
||
|
quantvals=maptype1_quantvals();
|
||
|
break;
|
||
|
case 2:
|
||
|
quantvals=entries*dim;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// quantized values
|
||
|
quantlist=new int[quantvals];
|
||
|
for(i=0;i<quantvals;i++)
|
||
|
{
|
||
|
quantlist[i]=opb.read(q_quant);
|
||
|
}
|
||
|
if(quantlist[quantvals-1]==-1)
|
||
|
{
|
||
|
// goto _eofout;
|
||
|
clear();
|
||
|
return(-1);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
// goto _eofout;
|
||
|
clear();
|
||
|
return(-1);
|
||
|
}
|
||
|
// all set
|
||
|
return(0);
|
||
|
// _errout:
|
||
|
// _eofout:
|
||
|
// vorbis_staticbook_clear(s);
|
||
|
// return(-1);
|
||
|
}
|
||
|
|
||
|
// there might be a straightforward one-line way to do the below
|
||
|
// that's portable and totally safe against roundoff, but I haven't
|
||
|
// thought of it. Therefore, we opt on the side of caution
|
||
|
internal int maptype1_quantvals()
|
||
|
{
|
||
|
int vals=(int)(Math.Floor(Math.Pow(entries,1.0/dim)));
|
||
|
|
||
|
// the above *should* be reliable, but we'll not assume that FP is
|
||
|
// ever reliable when bitstream sync is at stake; verify via integer
|
||
|
// means that vals really is the greatest value of dim for which
|
||
|
// vals^b->bim <= b->entries
|
||
|
// treat the above as an initial guess
|
||
|
while(true)
|
||
|
{
|
||
|
int acc=1;
|
||
|
int acc1=1;
|
||
|
for(int i=0;i<dim;i++)
|
||
|
{
|
||
|
acc*=vals;
|
||
|
acc1*=vals+1;
|
||
|
}
|
||
|
if(acc<=entries && acc1>entries){ return(vals); }
|
||
|
else
|
||
|
{
|
||
|
if(acc>entries){ vals--; }
|
||
|
else{ vals++; }
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void clear()
|
||
|
{
|
||
|
// if(quantlist!=null)free(b->quantlist);
|
||
|
// if(lengthlist!=null)free(b->lengthlist);
|
||
|
// if(nearest_tree!=null){
|
||
|
// free(b->nearest_tree->ptr0);
|
||
|
// free(b->nearest_tree->ptr1);
|
||
|
// free(b->nearest_tree->p);
|
||
|
// free(b->nearest_tree->q);
|
||
|
// memset(b->nearest_tree,0,sizeof(encode_aux_nearestmatch));
|
||
|
// free(b->nearest_tree);
|
||
|
// }
|
||
|
// if(thresh_tree!=null){
|
||
|
// free(b->thresh_tree->quantthresh);
|
||
|
// free(b->thresh_tree->quantmap);
|
||
|
// memset(b->thresh_tree,0,sizeof(encode_aux_threshmatch));
|
||
|
// free(b->thresh_tree);
|
||
|
// }
|
||
|
// memset(b,0,sizeof(static_codebook));
|
||
|
}
|
||
|
|
||
|
// unpack the quantized list of values for encode/decode
|
||
|
// we need to deal with two map types: in map type 1, the values are
|
||
|
// generated algorithmically (each column of the vector counts through
|
||
|
// the values in the quant vector). in map type 2, all the values came
|
||
|
// in in an explicit list. Both value lists must be unpacked
|
||
|
internal float[] unquantize()
|
||
|
{
|
||
|
|
||
|
if(maptype==1 || maptype==2)
|
||
|
{
|
||
|
int quantvals;
|
||
|
float mindel=float32_unpack(q_min);
|
||
|
float delta=float32_unpack(q_delta);
|
||
|
float[] r=new float[entries*dim];
|
||
|
|
||
|
//System.err.println("q_min="+q_min+", mindel="+mindel);
|
||
|
|
||
|
// maptype 1 and 2 both use a quantized value vector, but
|
||
|
// different sizes
|
||
|
switch(maptype)
|
||
|
{
|
||
|
case 1:
|
||
|
// most of the time, entries%dimensions == 0, but we need to be
|
||
|
// well defined. We define that the possible vales at each
|
||
|
// scalar is values == entries/dim. If entries%dim != 0, we'll
|
||
|
// have 'too few' values (values*dim<entries), which means that
|
||
|
// we'll have 'left over' entries; left over entries use zeroed
|
||
|
// values (and are wasted). So don't generate codebooks like that
|
||
|
quantvals=maptype1_quantvals();
|
||
|
for(int j=0;j<entries;j++)
|
||
|
{
|
||
|
float last=0.0f;
|
||
|
int indexdiv=1;
|
||
|
for(int k=0;k<dim;k++)
|
||
|
{
|
||
|
int index=(j/indexdiv)%quantvals;
|
||
|
float val=quantlist[index];
|
||
|
val=Math.Abs(val)*delta+mindel+last;
|
||
|
if(q_sequencep!=0)last=val;
|
||
|
r[j*dim+k]=val;
|
||
|
indexdiv*=quantvals;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case 2:
|
||
|
for(int j=0;j<entries;j++)
|
||
|
{
|
||
|
float last=0.0f;
|
||
|
for(int k=0;k<dim;k++)
|
||
|
{
|
||
|
float val=quantlist[j*dim+k];
|
||
|
//if((j*dim+k)==0){System.err.println(" | 0 -> "+val+" | ");}
|
||
|
val=Math.Abs(val)*delta+mindel+last;
|
||
|
if(q_sequencep!=0)last=val;
|
||
|
r[j*dim+k]=val;
|
||
|
//if((j*dim+k)==0){System.err.println(" $ r[0] -> "+r[0]+" | ");}
|
||
|
}
|
||
|
}
|
||
|
//System.err.println("\nr[0]="+r[0]);
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
return(r);
|
||
|
}
|
||
|
return(null);
|
||
|
}
|
||
|
|
||
|
internal static int ilog(int v)
|
||
|
{
|
||
|
int ret=0;
|
||
|
while(v!=0)
|
||
|
{
|
||
|
ret++;
|
||
|
v = (int)((uint)v >> 1);
|
||
|
}
|
||
|
return(ret);
|
||
|
}
|
||
|
|
||
|
// 32 bit float (not IEEE; nonnormalized mantissa +
|
||
|
// biased exponent) : neeeeeee eeemmmmm mmmmmmmm mmmmmmmm
|
||
|
// Why not IEEE? It's just not that important here.
|
||
|
|
||
|
//static int VQ_FEXP=10;
|
||
|
static int VQ_FMAN=21;
|
||
|
static int VQ_FEXP_BIAS=768; // bias toward values smaller than 1.
|
||
|
|
||
|
// doesn't currently guard under/overflow
|
||
|
internal static long float32_pack(float val)
|
||
|
{
|
||
|
uint sign=0;
|
||
|
int exp;
|
||
|
int mant;
|
||
|
if(val<0)
|
||
|
{
|
||
|
sign = 0x80000000;
|
||
|
val = -val;
|
||
|
}
|
||
|
exp=(int)Math.Floor(Math.Log(val)/Math.Log(2));
|
||
|
mant=(int)Math.Round(Math.Pow(val,(VQ_FMAN-1)-exp));
|
||
|
exp=(exp+VQ_FEXP_BIAS)<<VQ_FMAN;
|
||
|
return((int)sign | exp | mant);
|
||
|
}
|
||
|
|
||
|
internal static float float32_unpack(int val)
|
||
|
{
|
||
|
float mant=val&0x1fffff;
|
||
|
float sign=val&0x80000000;
|
||
|
float exp =(uint)(val&0x7fe00000) >> VQ_FMAN;
|
||
|
//System.err.println("mant="+mant+", sign="+sign+", exp="+exp);
|
||
|
//if(sign!=0.0)mant= -mant;
|
||
|
if((val&0x80000000)!=0)mant= -mant;
|
||
|
//System.err.println("mant="+mant);
|
||
|
return(ldexp(mant,((int)exp)-(VQ_FMAN-1)-VQ_FEXP_BIAS));
|
||
|
}
|
||
|
|
||
|
internal static float ldexp(float foo, int e)
|
||
|
{
|
||
|
return (float)(foo*Math.Pow(2, e));
|
||
|
}
|
||
|
}
|
||
|
}
|