| /******************************************************************** |
| * * |
| * 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-2001 * |
| * by the Xiph.Org Foundation http://www.xiph.org/ * |
| * * |
| ******************************************************************** |
| |
| function: train a VQ codebook |
| last mod: $Id: vqgen.c 16037 2009-05-26 21:10:58Z xiphmont $ |
| |
| ********************************************************************/ |
| |
| /* This code is *not* part of libvorbis. It is used to generate |
| trained codebooks offline and then spit the results into a |
| pregenerated codebook that is compiled into libvorbis. It is an |
| expensive (but good) algorithm. Run it on big iron. */ |
| |
| /* There are so many optimizations to explore in *both* stages that |
| considering the undertaking is almost withering. For now, we brute |
| force it all */ |
| |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <math.h> |
| #include <string.h> |
| |
| #include "vqgen.h" |
| #include "bookutil.h" |
| |
| /* Codebook generation happens in two steps: |
| |
| 1) Train the codebook with data collected from the encoder: We use |
| one of a few error metrics (which represent the distance between a |
| given data point and a candidate point in the training set) to |
| divide the training set up into cells representing roughly equal |
| probability of occurring. |
| |
| 2) Generate the codebook and auxiliary data from the trained data set |
| */ |
| |
| /* Codebook training **************************************************** |
| * |
| * The basic idea here is that a VQ codebook is like an m-dimensional |
| * foam with n bubbles. The bubbles compete for space/volume and are |
| * 'pressurized' [biased] according to some metric. The basic alg |
| * iterates through allowing the bubbles to compete for space until |
| * they converge (if the damping is dome properly) on a steady-state |
| * solution. Individual input points, collected from libvorbis, are |
| * used to train the algorithm monte-carlo style. */ |
| |
| /* internal helpers *****************************************************/ |
| #define vN(data,i) (data+v->elements*i) |
| |
| /* default metric; squared 'distance' from desired value. */ |
| float _dist(vqgen *v,float *a, float *b){ |
| int i; |
| int el=v->elements; |
| float acc=0.f; |
| for(i=0;i<el;i++){ |
| float val=(a[i]-b[i]); |
| acc+=val*val; |
| } |
| return sqrt(acc); |
| } |
| |
| float *_weight_null(vqgen *v,float *a){ |
| return a; |
| } |
| |
| /* *must* be beefed up. */ |
| void _vqgen_seed(vqgen *v){ |
| long i; |
| for(i=0;i<v->entries;i++) |
| memcpy(_now(v,i),_point(v,i),sizeof(float)*v->elements); |
| v->seeded=1; |
| } |
| |
| int directdsort(const void *a, const void *b){ |
| float av=*((float *)a); |
| float bv=*((float *)b); |
| return (av<bv)-(av>bv); |
| } |
| |
| void vqgen_cellmetric(vqgen *v){ |
| int j,k; |
| float min=-1.f,max=-1.f,mean=0.f,acc=0.f; |
| long dup=0,unused=0; |
| #ifdef NOISY |
| int i; |
| char buff[80]; |
| float spacings[v->entries]; |
| int count=0; |
| FILE *cells; |
| sprintf(buff,"cellspace%d.m",v->it); |
| cells=fopen(buff,"w"); |
| #endif |
| |
| /* minimum, maximum, cell spacing */ |
| for(j=0;j<v->entries;j++){ |
| float localmin=-1.; |
| |
| for(k=0;k<v->entries;k++){ |
| if(j!=k){ |
| float this=_dist(v,_now(v,j),_now(v,k)); |
| if(this>0){ |
| if(v->assigned[k] && (localmin==-1 || this<localmin)) |
| localmin=this; |
| }else{ |
| if(k<j){ |
| dup++; |
| break; |
| } |
| } |
| } |
| } |
| if(k<v->entries)continue; |
| |
| if(v->assigned[j]==0){ |
| unused++; |
| continue; |
| } |
| |
| localmin=v->max[j]+localmin/2; /* this gives us rough diameter */ |
| if(min==-1 || localmin<min)min=localmin; |
| if(max==-1 || localmin>max)max=localmin; |
| mean+=localmin; |
| acc++; |
| #ifdef NOISY |
| spacings[count++]=localmin; |
| #endif |
| } |
| |
| fprintf(stderr,"cell diameter: %.03g::%.03g::%.03g (%ld unused/%ld dup)\n", |
| min,mean/acc,max,unused,dup); |
| |
| #ifdef NOISY |
| qsort(spacings,count,sizeof(float),directdsort); |
| for(i=0;i<count;i++) |
| fprintf(cells,"%g\n",spacings[i]); |
| fclose(cells); |
| #endif |
| |
| } |
| |
| /* External calls *******************************************************/ |
| |
| /* We have two forms of quantization; in the first, each vector |
| element in the codebook entry is orthogonal. Residues would use this |
| quantization for example. |
| |
| In the second, we have a sequence of monotonically increasing |
| values that we wish to quantize as deltas (to save space). We |
| still need to quantize so that absolute values are accurate. For |
| example, LSP quantizes all absolute values, but the book encodes |
| distance between values because each successive value is larger |
| than the preceeding value. Thus the desired quantibits apply to |
| the encoded (delta) values, not abs positions. This requires minor |
| additional encode-side trickery. */ |
| |
| void vqgen_quantize(vqgen *v,quant_meta *q){ |
| |
| float maxdel; |
| float mindel; |
| |
| float delta; |
| float maxquant=((1<<q->quant)-1); |
| |
| int j,k; |
| |
| mindel=maxdel=_now(v,0)[0]; |
| |
| for(j=0;j<v->entries;j++){ |
| float last=0.f; |
| for(k=0;k<v->elements;k++){ |
| if(mindel>_now(v,j)[k]-last)mindel=_now(v,j)[k]-last; |
| if(maxdel<_now(v,j)[k]-last)maxdel=_now(v,j)[k]-last; |
| if(q->sequencep)last=_now(v,j)[k]; |
| } |
| } |
| |
| |
| /* first find the basic delta amount from the maximum span to be |
| encoded. Loosen the delta slightly to allow for additional error |
| during sequence quantization */ |
| |
| delta=(maxdel-mindel)/((1<<q->quant)-1.5f); |
| |
| q->min=_float32_pack(mindel); |
| q->delta=_float32_pack(delta); |
| |
| mindel=_float32_unpack(q->min); |
| delta=_float32_unpack(q->delta); |
| |
| for(j=0;j<v->entries;j++){ |
| float last=0; |
| for(k=0;k<v->elements;k++){ |
| float val=_now(v,j)[k]; |
| float now=rint((val-last-mindel)/delta); |
| |
| _now(v,j)[k]=now; |
| if(now<0){ |
| /* be paranoid; this should be impossible */ |
| fprintf(stderr,"fault; quantized value<0\n"); |
| exit(1); |
| } |
| |
| if(now>maxquant){ |
| /* be paranoid; this should be impossible */ |
| fprintf(stderr,"fault; quantized value>max\n"); |
| exit(1); |
| } |
| if(q->sequencep)last=(now*delta)+mindel+last; |
| } |
| } |
| } |
| |
| /* much easier :-). Unlike in the codebook, we don't un-log log |
| scales; we just make sure they're properly offset. */ |
| void vqgen_unquantize(vqgen *v,quant_meta *q){ |
| long j,k; |
| float mindel=_float32_unpack(q->min); |
| float delta=_float32_unpack(q->delta); |
| |
| for(j=0;j<v->entries;j++){ |
| float last=0.f; |
| for(k=0;k<v->elements;k++){ |
| float now=_now(v,j)[k]; |
| now=fabs(now)*delta+last+mindel; |
| if(q->sequencep)last=now; |
| _now(v,j)[k]=now; |
| } |
| } |
| } |
| |
| void vqgen_init(vqgen *v,int elements,int aux,int entries,float mindist, |
| float (*metric)(vqgen *,float *, float *), |
| float *(*weight)(vqgen *,float *),int centroid){ |
| memset(v,0,sizeof(vqgen)); |
| |
| v->centroid=centroid; |
| v->elements=elements; |
| v->aux=aux; |
| v->mindist=mindist; |
| v->allocated=32768; |
| v->pointlist=_ogg_malloc(v->allocated*(v->elements+v->aux)*sizeof(float)); |
| |
| v->entries=entries; |
| v->entrylist=_ogg_malloc(v->entries*v->elements*sizeof(float)); |
| v->assigned=_ogg_malloc(v->entries*sizeof(long)); |
| v->bias=_ogg_calloc(v->entries,sizeof(float)); |
| v->max=_ogg_calloc(v->entries,sizeof(float)); |
| if(metric) |
| v->metric_func=metric; |
| else |
| v->metric_func=_dist; |
| if(weight) |
| v->weight_func=weight; |
| else |
| v->weight_func=_weight_null; |
| |
| v->asciipoints=tmpfile(); |
| |
| } |
| |
| void vqgen_addpoint(vqgen *v, float *p,float *a){ |
| int k; |
| for(k=0;k<v->elements;k++) |
| fprintf(v->asciipoints,"%.12g\n",p[k]); |
| for(k=0;k<v->aux;k++) |
| fprintf(v->asciipoints,"%.12g\n",a[k]); |
| |
| if(v->points>=v->allocated){ |
| v->allocated*=2; |
| v->pointlist=_ogg_realloc(v->pointlist,v->allocated*(v->elements+v->aux)* |
| sizeof(float)); |
| } |
| |
| memcpy(_point(v,v->points),p,sizeof(float)*v->elements); |
| if(v->aux)memcpy(_point(v,v->points)+v->elements,a,sizeof(float)*v->aux); |
| |
| /* quantize to the density mesh if it's selected */ |
| if(v->mindist>0.f){ |
| /* quantize to the mesh */ |
| for(k=0;k<v->elements+v->aux;k++) |
| _point(v,v->points)[k]= |
| rint(_point(v,v->points)[k]/v->mindist)*v->mindist; |
| } |
| v->points++; |
| if(!(v->points&0xff))spinnit("loading... ",v->points); |
| } |
| |
| /* yes, not threadsafe. These utils aren't */ |
| static int sortit=0; |
| static int sortsize=0; |
| static int meshcomp(const void *a,const void *b){ |
| if(((sortit++)&0xfff)==0)spinnit("sorting mesh...",sortit); |
| return(memcmp(a,b,sortsize)); |
| } |
| |
| void vqgen_sortmesh(vqgen *v){ |
| sortit=0; |
| if(v->mindist>0.f){ |
| long i,march=1; |
| |
| /* sort to make uniqueness detection trivial */ |
| sortsize=(v->elements+v->aux)*sizeof(float); |
| qsort(v->pointlist,v->points,sortsize,meshcomp); |
| |
| /* now march through and eliminate dupes */ |
| for(i=1;i<v->points;i++){ |
| if(memcmp(_point(v,i),_point(v,i-1),sortsize)){ |
| /* a new, unique entry. march it down */ |
| if(i>march)memcpy(_point(v,march),_point(v,i),sortsize); |
| march++; |
| } |
| spinnit("eliminating density... ",v->points-i); |
| } |
| |
| /* we're done */ |
| fprintf(stderr,"\r%ld training points remining out of %ld" |
| " after density mesh (%ld%%)\n",march,v->points,march*100/v->points); |
| v->points=march; |
| |
| } |
| v->sorted=1; |
| } |
| |
| float vqgen_iterate(vqgen *v,int biasp){ |
| long i,j,k; |
| |
| float fdesired; |
| long desired; |
| long desired2; |
| |
| float asserror=0.f; |
| float meterror=0.f; |
| float *new; |
| float *new2; |
| long *nearcount; |
| float *nearbias; |
| #ifdef NOISY |
| char buff[80]; |
| FILE *assig; |
| FILE *bias; |
| FILE *cells; |
| sprintf(buff,"cells%d.m",v->it); |
| cells=fopen(buff,"w"); |
| sprintf(buff,"assig%d.m",v->it); |
| assig=fopen(buff,"w"); |
| sprintf(buff,"bias%d.m",v->it); |
| bias=fopen(buff,"w"); |
| #endif |
| |
| |
| if(v->entries<2){ |
| fprintf(stderr,"generation requires at least two entries\n"); |
| exit(1); |
| } |
| |
| if(!v->sorted)vqgen_sortmesh(v); |
| if(!v->seeded)_vqgen_seed(v); |
| |
| fdesired=(float)v->points/v->entries; |
| desired=fdesired; |
| desired2=desired*2; |
| new=_ogg_malloc(sizeof(float)*v->entries*v->elements); |
| new2=_ogg_malloc(sizeof(float)*v->entries*v->elements); |
| nearcount=_ogg_malloc(v->entries*sizeof(long)); |
| nearbias=_ogg_malloc(v->entries*desired2*sizeof(float)); |
| |
| /* fill in nearest points for entry biasing */ |
| /*memset(v->bias,0,sizeof(float)*v->entries);*/ |
| memset(nearcount,0,sizeof(long)*v->entries); |
| memset(v->assigned,0,sizeof(long)*v->entries); |
| if(biasp){ |
| for(i=0;i<v->points;i++){ |
| float *ppt=v->weight_func(v,_point(v,i)); |
| float firstmetric=v->metric_func(v,_now(v,0),ppt)+v->bias[0]; |
| float secondmetric=v->metric_func(v,_now(v,1),ppt)+v->bias[1]; |
| long firstentry=0; |
| long secondentry=1; |
| |
| if(!(i&0xff))spinnit("biasing... ",v->points+v->points+v->entries-i); |
| |
| if(firstmetric>secondmetric){ |
| float temp=firstmetric; |
| firstmetric=secondmetric; |
| secondmetric=temp; |
| firstentry=1; |
| secondentry=0; |
| } |
| |
| for(j=2;j<v->entries;j++){ |
| float thismetric=v->metric_func(v,_now(v,j),ppt)+v->bias[j]; |
| if(thismetric<secondmetric){ |
| if(thismetric<firstmetric){ |
| secondmetric=firstmetric; |
| secondentry=firstentry; |
| firstmetric=thismetric; |
| firstentry=j; |
| }else{ |
| secondmetric=thismetric; |
| secondentry=j; |
| } |
| } |
| } |
| |
| j=firstentry; |
| for(j=0;j<v->entries;j++){ |
| |
| float thismetric,localmetric; |
| float *nearbiasptr=nearbias+desired2*j; |
| long k=nearcount[j]; |
| |
| localmetric=v->metric_func(v,_now(v,j),ppt); |
| /* 'thismetric' is to be the bias value necessary in the current |
| arrangement for entry j to capture point i */ |
| if(firstentry==j){ |
| /* use the secondary entry as the threshhold */ |
| thismetric=secondmetric-localmetric; |
| }else{ |
| /* use the primary entry as the threshhold */ |
| thismetric=firstmetric-localmetric; |
| } |
| |
| /* support the idea of 'minimum distance'... if we want the |
| cells in a codebook to be roughly some minimum size (as with |
| the low resolution residue books) */ |
| |
| /* a cute two-stage delayed sorting hack */ |
| if(k<desired){ |
| nearbiasptr[k]=thismetric; |
| k++; |
| if(k==desired){ |
| spinnit("biasing... ",v->points+v->points+v->entries-i); |
| qsort(nearbiasptr,desired,sizeof(float),directdsort); |
| } |
| |
| }else if(thismetric>nearbiasptr[desired-1]){ |
| nearbiasptr[k]=thismetric; |
| k++; |
| if(k==desired2){ |
| spinnit("biasing... ",v->points+v->points+v->entries-i); |
| qsort(nearbiasptr,desired2,sizeof(float),directdsort); |
| k=desired; |
| } |
| } |
| nearcount[j]=k; |
| } |
| } |
| |
| /* inflate/deflate */ |
| |
| for(i=0;i<v->entries;i++){ |
| float *nearbiasptr=nearbias+desired2*i; |
| |
| spinnit("biasing... ",v->points+v->entries-i); |
| |
| /* due to the delayed sorting, we likely need to finish it off....*/ |
| if(nearcount[i]>desired) |
| qsort(nearbiasptr,nearcount[i],sizeof(float),directdsort); |
| |
| v->bias[i]=nearbiasptr[desired-1]; |
| |
| } |
| }else{ |
| memset(v->bias,0,v->entries*sizeof(float)); |
| } |
| |
| /* Now assign with new bias and find new midpoints */ |
| for(i=0;i<v->points;i++){ |
| float *ppt=v->weight_func(v,_point(v,i)); |
| float firstmetric=v->metric_func(v,_now(v,0),ppt)+v->bias[0]; |
| long firstentry=0; |
| |
| if(!(i&0xff))spinnit("centering... ",v->points-i); |
| |
| for(j=0;j<v->entries;j++){ |
| float thismetric=v->metric_func(v,_now(v,j),ppt)+v->bias[j]; |
| if(thismetric<firstmetric){ |
| firstmetric=thismetric; |
| firstentry=j; |
| } |
| } |
| |
| j=firstentry; |
| |
| #ifdef NOISY |
| fprintf(cells,"%g %g\n%g %g\n\n", |
| _now(v,j)[0],_now(v,j)[1], |
| ppt[0],ppt[1]); |
| #endif |
| |
| firstmetric-=v->bias[j]; |
| meterror+=firstmetric; |
| |
| if(v->centroid==0){ |
| /* set up midpoints for next iter */ |
| if(v->assigned[j]++){ |
| for(k=0;k<v->elements;k++) |
| vN(new,j)[k]+=ppt[k]; |
| if(firstmetric>v->max[j])v->max[j]=firstmetric; |
| }else{ |
| for(k=0;k<v->elements;k++) |
| vN(new,j)[k]=ppt[k]; |
| v->max[j]=firstmetric; |
| } |
| }else{ |
| /* centroid */ |
| if(v->assigned[j]++){ |
| for(k=0;k<v->elements;k++){ |
| if(vN(new,j)[k]>ppt[k])vN(new,j)[k]=ppt[k]; |
| if(vN(new2,j)[k]<ppt[k])vN(new2,j)[k]=ppt[k]; |
| } |
| if(firstmetric>v->max[firstentry])v->max[j]=firstmetric; |
| }else{ |
| for(k=0;k<v->elements;k++){ |
| vN(new,j)[k]=ppt[k]; |
| vN(new2,j)[k]=ppt[k]; |
| } |
| v->max[firstentry]=firstmetric; |
| } |
| } |
| } |
| |
| /* assign midpoints */ |
| |
| for(j=0;j<v->entries;j++){ |
| #ifdef NOISY |
| fprintf(assig,"%ld\n",v->assigned[j]); |
| fprintf(bias,"%g\n",v->bias[j]); |
| #endif |
| asserror+=fabs(v->assigned[j]-fdesired); |
| if(v->assigned[j]){ |
| if(v->centroid==0){ |
| for(k=0;k<v->elements;k++) |
| _now(v,j)[k]=vN(new,j)[k]/v->assigned[j]; |
| }else{ |
| for(k=0;k<v->elements;k++) |
| _now(v,j)[k]=(vN(new,j)[k]+vN(new2,j)[k])/2.f; |
| } |
| } |
| } |
| |
| asserror/=(v->entries*fdesired); |
| |
| fprintf(stderr,"Pass #%d... ",v->it); |
| fprintf(stderr,": dist %g(%g) metric error=%g \n", |
| asserror,fdesired,meterror/v->points); |
| v->it++; |
| |
| free(new); |
| free(nearcount); |
| free(nearbias); |
| #ifdef NOISY |
| fclose(assig); |
| fclose(bias); |
| fclose(cells); |
| #endif |
| return(asserror); |
| } |
| |