1 /********************************************************************
3 * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. *
4 * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS *
5 * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE *
6 * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. *
8 * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2001 *
9 * by the Xiph.Org Foundation http://www.xiph.org/ *
11 ********************************************************************
13 function: train a VQ codebook
15 ********************************************************************/
17 /* This code is *not* part of libvorbis. It is used to generate
18 trained codebooks offline and then spit the results into a
19 pregenerated codebook that is compiled into libvorbis. It is an
20 expensive (but good) algorithm. Run it on big iron. */
22 /* There are so many optimizations to explore in *both* stages that
23 considering the undertaking is almost withering. For now, we brute
34 /* Codebook generation happens in two steps:
36 1) Train the codebook with data collected from the encoder: We use
37 one of a few error metrics (which represent the distance between a
38 given data point and a candidate point in the training set) to
39 divide the training set up into cells representing roughly equal
40 probability of occurring.
42 2) Generate the codebook and auxiliary data from the trained data set
45 /* Codebook training ****************************************************
47 * The basic idea here is that a VQ codebook is like an m-dimensional
48 * foam with n bubbles. The bubbles compete for space/volume and are
49 * 'pressurized' [biased] according to some metric. The basic alg
50 * iterates through allowing the bubbles to compete for space until
51 * they converge (if the damping is dome properly) on a steady-state
52 * solution. Individual input points, collected from libvorbis, are
53 * used to train the algorithm monte-carlo style. */
55 /* internal helpers *****************************************************/
56 #define vN(data,i) (data+v->elements*i)
58 /* default metric; squared 'distance' from desired value. */
59 float _dist(vqgen *v,float *a, float *b){
64 float val=(a[i]-b[i]);
70 float *_weight_null(vqgen *v,float *a){
74 /* *must* be beefed up. */
75 void _vqgen_seed(vqgen *v){
77 for(i=0;i<v->entries;i++)
78 memcpy(_now(v,i),_point(v,i),sizeof(float)*v->elements);
82 int directdsort(const void *a, const void *b){
83 float av=*((float *)a);
84 float bv=*((float *)b);
85 return (av<bv)-(av>bv);
88 void vqgen_cellmetric(vqgen *v){
90 float min=-1.f,max=-1.f,mean=0.f,acc=0.f;
95 float spacings[v->entries];
98 sprintf(buff,"cellspace%d.m",v->it);
99 cells=fopen(buff,"w");
102 /* minimum, maximum, cell spacing */
103 for(j=0;j<v->entries;j++){
106 for(k=0;k<v->entries;k++){
108 float this=_dist(v,_now(v,j),_now(v,k));
110 if(v->assigned[k] && (localmin==-1 || this<localmin))
120 if(k<v->entries)continue;
122 if(v->assigned[j]==0){
127 localmin=v->max[j]+localmin/2; /* this gives us rough diameter */
128 if(min==-1 || localmin<min)min=localmin;
129 if(max==-1 || localmin>max)max=localmin;
133 spacings[count++]=localmin;
137 fprintf(stderr,"cell diameter: %.03g::%.03g::%.03g (%ld unused/%ld dup)\n",
138 min,mean/acc,max,unused,dup);
141 qsort(spacings,count,sizeof(float),directdsort);
143 fprintf(cells,"%g\n",spacings[i]);
149 /* External calls *******************************************************/
151 /* We have two forms of quantization; in the first, each vector
152 element in the codebook entry is orthogonal. Residues would use this
153 quantization for example.
155 In the second, we have a sequence of monotonically increasing
156 values that we wish to quantize as deltas (to save space). We
157 still need to quantize so that absolute values are accurate. For
158 example, LSP quantizes all absolute values, but the book encodes
159 distance between values because each successive value is larger
160 than the preceeding value. Thus the desired quantibits apply to
161 the encoded (delta) values, not abs positions. This requires minor
162 additional encode-side trickery. */
164 void vqgen_quantize(vqgen *v,quant_meta *q){
170 float maxquant=((1<<q->quant)-1);
174 mindel=maxdel=_now(v,0)[0];
176 for(j=0;j<v->entries;j++){
178 for(k=0;k<v->elements;k++){
179 if(mindel>_now(v,j)[k]-last)mindel=_now(v,j)[k]-last;
180 if(maxdel<_now(v,j)[k]-last)maxdel=_now(v,j)[k]-last;
181 if(q->sequencep)last=_now(v,j)[k];
186 /* first find the basic delta amount from the maximum span to be
187 encoded. Loosen the delta slightly to allow for additional error
188 during sequence quantization */
190 delta=(maxdel-mindel)/((1<<q->quant)-1.5f);
192 q->min=_float32_pack(mindel);
193 q->delta=_float32_pack(delta);
195 mindel=_float32_unpack(q->min);
196 delta=_float32_unpack(q->delta);
198 for(j=0;j<v->entries;j++){
200 for(k=0;k<v->elements;k++){
201 float val=_now(v,j)[k];
202 float now=rint((val-last-mindel)/delta);
206 /* be paranoid; this should be impossible */
207 fprintf(stderr,"fault; quantized value<0\n");
212 /* be paranoid; this should be impossible */
213 fprintf(stderr,"fault; quantized value>max\n");
216 if(q->sequencep)last=(now*delta)+mindel+last;
221 /* much easier :-). Unlike in the codebook, we don't un-log log
222 scales; we just make sure they're properly offset. */
223 void vqgen_unquantize(vqgen *v,quant_meta *q){
225 float mindel=_float32_unpack(q->min);
226 float delta=_float32_unpack(q->delta);
228 for(j=0;j<v->entries;j++){
230 for(k=0;k<v->elements;k++){
231 float now=_now(v,j)[k];
232 now=fabs(now)*delta+last+mindel;
233 if(q->sequencep)last=now;
239 void vqgen_init(vqgen *v,int elements,int aux,int entries,float mindist,
240 float (*metric)(vqgen *,float *, float *),
241 float *(*weight)(vqgen *,float *),int centroid){
242 memset(v,0,sizeof(vqgen));
244 v->centroid=centroid;
245 v->elements=elements;
249 v->pointlist=_ogg_malloc(v->allocated*(v->elements+v->aux)*sizeof(float));
252 v->entrylist=_ogg_malloc(v->entries*v->elements*sizeof(float));
253 v->assigned=_ogg_malloc(v->entries*sizeof(long));
254 v->bias=_ogg_calloc(v->entries,sizeof(float));
255 v->max=_ogg_calloc(v->entries,sizeof(float));
257 v->metric_func=metric;
259 v->metric_func=_dist;
261 v->weight_func=weight;
263 v->weight_func=_weight_null;
265 v->asciipoints=tmpfile();
269 void vqgen_addpoint(vqgen *v, float *p,float *a){
271 for(k=0;k<v->elements;k++)
272 fprintf(v->asciipoints,"%.12g\n",p[k]);
273 for(k=0;k<v->aux;k++)
274 fprintf(v->asciipoints,"%.12g\n",a[k]);
276 if(v->points>=v->allocated){
278 v->pointlist=_ogg_realloc(v->pointlist,v->allocated*(v->elements+v->aux)*
282 memcpy(_point(v,v->points),p,sizeof(float)*v->elements);
283 if(v->aux)memcpy(_point(v,v->points)+v->elements,a,sizeof(float)*v->aux);
285 /* quantize to the density mesh if it's selected */
287 /* quantize to the mesh */
288 for(k=0;k<v->elements+v->aux;k++)
289 _point(v,v->points)[k]=
290 rint(_point(v,v->points)[k]/v->mindist)*v->mindist;
293 if(!(v->points&0xff))spinnit("loading... ",v->points);
296 /* yes, not threadsafe. These utils aren't */
298 static int sortsize=0;
299 static int meshcomp(const void *a,const void *b){
300 if(((sortit++)&0xfff)==0)spinnit("sorting mesh...",sortit);
301 return(memcmp(a,b,sortsize));
304 void vqgen_sortmesh(vqgen *v){
309 /* sort to make uniqueness detection trivial */
310 sortsize=(v->elements+v->aux)*sizeof(float);
311 qsort(v->pointlist,v->points,sortsize,meshcomp);
313 /* now march through and eliminate dupes */
314 for(i=1;i<v->points;i++){
315 if(memcmp(_point(v,i),_point(v,i-1),sortsize)){
316 /* a new, unique entry. march it down */
317 if(i>march)memcpy(_point(v,march),_point(v,i),sortsize);
320 spinnit("eliminating density... ",v->points-i);
324 fprintf(stderr,"\r%ld training points remining out of %ld"
325 " after density mesh (%ld%%)\n",march,v->points,march*100/v->points);
332 float vqgen_iterate(vqgen *v,int biasp){
350 sprintf(buff,"cells%d.m",v->it);
351 cells=fopen(buff,"w");
352 sprintf(buff,"assig%d.m",v->it);
353 assig=fopen(buff,"w");
354 sprintf(buff,"bias%d.m",v->it);
355 bias=fopen(buff,"w");
360 fprintf(stderr,"generation requires at least two entries\n");
364 if(!v->sorted)vqgen_sortmesh(v);
365 if(!v->seeded)_vqgen_seed(v);
367 fdesired=(float)v->points/v->entries;
370 new=_ogg_malloc(sizeof(float)*v->entries*v->elements);
371 new2=_ogg_malloc(sizeof(float)*v->entries*v->elements);
372 nearcount=_ogg_malloc(v->entries*sizeof(long));
373 nearbias=_ogg_malloc(v->entries*desired2*sizeof(float));
375 /* fill in nearest points for entry biasing */
376 /*memset(v->bias,0,sizeof(float)*v->entries);*/
377 memset(nearcount,0,sizeof(long)*v->entries);
378 memset(v->assigned,0,sizeof(long)*v->entries);
380 for(i=0;i<v->points;i++){
381 float *ppt=v->weight_func(v,_point(v,i));
382 float firstmetric=v->metric_func(v,_now(v,0),ppt)+v->bias[0];
383 float secondmetric=v->metric_func(v,_now(v,1),ppt)+v->bias[1];
387 if(!(i&0xff))spinnit("biasing... ",v->points+v->points+v->entries-i);
389 if(firstmetric>secondmetric){
390 float temp=firstmetric;
391 firstmetric=secondmetric;
397 for(j=2;j<v->entries;j++){
398 float thismetric=v->metric_func(v,_now(v,j),ppt)+v->bias[j];
399 if(thismetric<secondmetric){
400 if(thismetric<firstmetric){
401 secondmetric=firstmetric;
402 secondentry=firstentry;
403 firstmetric=thismetric;
406 secondmetric=thismetric;
413 for(j=0;j<v->entries;j++){
415 float thismetric,localmetric;
416 float *nearbiasptr=nearbias+desired2*j;
419 localmetric=v->metric_func(v,_now(v,j),ppt);
420 /* 'thismetric' is to be the bias value necessary in the current
421 arrangement for entry j to capture point i */
423 /* use the secondary entry as the threshhold */
424 thismetric=secondmetric-localmetric;
426 /* use the primary entry as the threshhold */
427 thismetric=firstmetric-localmetric;
430 /* support the idea of 'minimum distance'... if we want the
431 cells in a codebook to be roughly some minimum size (as with
432 the low resolution residue books) */
434 /* a cute two-stage delayed sorting hack */
436 nearbiasptr[k]=thismetric;
439 spinnit("biasing... ",v->points+v->points+v->entries-i);
440 qsort(nearbiasptr,desired,sizeof(float),directdsort);
443 }else if(thismetric>nearbiasptr[desired-1]){
444 nearbiasptr[k]=thismetric;
447 spinnit("biasing... ",v->points+v->points+v->entries-i);
448 qsort(nearbiasptr,desired2,sizeof(float),directdsort);
456 /* inflate/deflate */
458 for(i=0;i<v->entries;i++){
459 float *nearbiasptr=nearbias+desired2*i;
461 spinnit("biasing... ",v->points+v->entries-i);
463 /* due to the delayed sorting, we likely need to finish it off....*/
464 if(nearcount[i]>desired)
465 qsort(nearbiasptr,nearcount[i],sizeof(float),directdsort);
467 v->bias[i]=nearbiasptr[desired-1];
471 memset(v->bias,0,v->entries*sizeof(float));
474 /* Now assign with new bias and find new midpoints */
475 for(i=0;i<v->points;i++){
476 float *ppt=v->weight_func(v,_point(v,i));
477 float firstmetric=v->metric_func(v,_now(v,0),ppt)+v->bias[0];
480 if(!(i&0xff))spinnit("centering... ",v->points-i);
482 for(j=0;j<v->entries;j++){
483 float thismetric=v->metric_func(v,_now(v,j),ppt)+v->bias[j];
484 if(thismetric<firstmetric){
485 firstmetric=thismetric;
493 fprintf(cells,"%g %g\n%g %g\n\n",
494 _now(v,j)[0],_now(v,j)[1],
498 firstmetric-=v->bias[j];
499 meterror+=firstmetric;
502 /* set up midpoints for next iter */
503 if(v->assigned[j]++){
504 for(k=0;k<v->elements;k++)
505 vN(new,j)[k]+=ppt[k];
506 if(firstmetric>v->max[j])v->max[j]=firstmetric;
508 for(k=0;k<v->elements;k++)
510 v->max[j]=firstmetric;
514 if(v->assigned[j]++){
515 for(k=0;k<v->elements;k++){
516 if(vN(new,j)[k]>ppt[k])vN(new,j)[k]=ppt[k];
517 if(vN(new2,j)[k]<ppt[k])vN(new2,j)[k]=ppt[k];
519 if(firstmetric>v->max[firstentry])v->max[j]=firstmetric;
521 for(k=0;k<v->elements;k++){
523 vN(new2,j)[k]=ppt[k];
525 v->max[firstentry]=firstmetric;
530 /* assign midpoints */
532 for(j=0;j<v->entries;j++){
534 fprintf(assig,"%ld\n",v->assigned[j]);
535 fprintf(bias,"%g\n",v->bias[j]);
537 asserror+=fabs(v->assigned[j]-fdesired);
540 for(k=0;k<v->elements;k++)
541 _now(v,j)[k]=vN(new,j)[k]/v->assigned[j];
543 for(k=0;k<v->elements;k++)
544 _now(v,j)[k]=(vN(new,j)[k]+vN(new2,j)[k])/2.f;
549 asserror/=(v->entries*fdesired);
551 fprintf(stderr,"Pass #%d... ",v->it);
552 fprintf(stderr,": dist %g(%g) metric error=%g \n",
553 asserror,fdesired,meterror/v->points);