1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-10-09 16:13:41 +02:00
Files
.github
autotools
cmake-proxies
dox2-src
help
images
include
lib-src
FileDialog
expat
ffmpeg
header-substitutes
lame
lib-widget-extra
libflac
libid3tag
libmad
libmp3lame
libnyquist
libogg
libscorealign
libsndfile
libsoxr
libvamp
libvorbis
doc
examples
include
lib
m4
macos
macosx
symbian
test
vq
16.vqs
16u.vqs
44c-1.vqs
44c0.vqs
44c1.vqs
44c2.vqs
44c3.vqs
44c4.vqs
44c5.vqs
44c6.vqs
44c7.vqs
44c8.vqs
44c9.vqs
44p-1.vqs
44p0.vqs
44p1.vqs
44p2.vqs
44p3.vqs
44p4.vqs
44p5.vqs
44p6.vqs
44p7.vqs
44p8.vqs
44p9.vqs
44u0.vqs
44u1.vqs
44u2.vqs
44u3.vqs
44u4.vqs
44u5.vqs
44u6.vqs
44u7.vqs
44u8.vqs
44u9.vqs
8.vqs
8u.vqs
Makefile.am
Makefile.in
auxpartition.pl
bookutil.c
bookutil.h
build.c
cascade.c
distribution.c
floor_11.vqs
floor_22.vqs
floor_44.vqs
genericdata.c
huffbuild.c
latticebuild.c
latticehint.c
latticepare.c
latticetune.c
localcodebook.h
lspdata.c
make_floor_books.pl
make_residue_books.pl
metrics.c
residue_entropy
residuedata.c
residuesplit.c
run.c
train.c
vqext.h
vqgen.c
vqgen.h
vqsplit.c
vqsplit.h
win32
AUTHORS
CHANGES
COPYING
Makefile.am
Makefile.in
README
autogen.sh
autotools.patch
compile
config.guess
config.h.in
config.sub
configure
configure.ac
depcomp
install-sh
libvorbis.spec.in
local-libogg.patch
ltmain.sh
missing
no-docs-examples.patch
todo.txt
vorbis-uninstalled.pc.in
vorbis.m4
vorbis.pc.in
vorbisenc-uninstalled.pc.in
vorbisenc.pc.in
vorbisfile-uninstalled.pc.in
vorbisfile.pc.in
lv2
mod-null
mod-nyq-bench
mod-script-pipe
mod-track-panel
mpglib
portaudio-v19
portburn
portmidi
portmixer
portsmf
sbsms
soundtouch
twolame
Makefile.am
Makefile.in
audacity-patches.txt
dist-libsoxr.mk
dist-libvamp.mk
dist-portaudio.mk
linux
locale
m4
mac
nyq-po
nyquist
plug-ins
presets
qa
scripts
src
tests
win
.gitattributes
.gitignore
.travis.yml
ABOUT-NLS
CHANGELOG.txt
CMakeLists.txt
CODE_OF_CONDUCT.md
CONTRIBUTING.md
Directory.Build.props
INSTALL
LICENSE.txt
Makefile.am
Makefile.in
PULL_REQUEST_TEMPLATE.md
README.md
README.txt
appveyor.yml
audacity.dox
configure
configure.ac
todo.txt
audacity/lib-src/libvorbis/vq/vqgen.c
2013-10-24 18:24:47 +00:00

568 lines
16 KiB
C

/********************************************************************
* *
* 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);
}