/*
* PPM text predictive coding. * gse. 1999. */ /* Respecto de la versi’on anterior, una ventana deslizante es definida con el objetivo de reducir el consumo de memoria. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include "codec.h" #define UCHAR unsigned char #define SYMBOL unsigned char #define ORDER unsigned char #define CODE unsigned char #ifdef _HASH_INFO_ int num_colisiones=0; int num_tablas=0; #endif #ifdef _INFO_PREDICTION_ FILE *fo; #endif /**************************************************/ /* M A N E J O D E L A T A B L A H A S H */ /**************************************************/ /* La estructura de datos utilizada para manejar los contextos es la siguiente: Tenemos una tabla hash, en la que cada entrada es un puntero a un contexto. Cada contexto se compone de 4 componentes: (1) el orden del contexto, (2) un puntero a una lista enlazada que almacena el contexto mediante pares s’imbolo/recuento y (3) el array de caracteres que almacenan el contexto. Gra’ficamente: hash_table CONTEXT_TABLE +------+ +-------+------+-----------+ | ptr -|->| order | list | context[] | +------+ +-------+---|--+-----------+ | | v : : +--------+-------+------+ : : | symbol | count | next-|-> ... +--------+-------+------+ SYMBOL_NODE La tabla hash implementada permite la ocurrencia de colisiones. Cuando una ocurre (dos o m’as contextos comparten la misma posici’on dentro de la tabla) se realiza una b’usqueda secuencial a partir de la direcci’on de colisi’on. El n’umero de b’usquedas secuenciales m’aximo permitido se declara en la constante MAX_COLLISIONS. La tabla hash es est’atica, ya que no se permite que el n’umero de contextos aumente si la tabla est’a demasiado llena (esto ser’ia lo ideal, pero por ahora no se hace). El n’umero m’aximo de contextos permitidos es MAX_CONTEXTS. */ struct SYMBOL_NODE { UCHAR symbol; UCHAR count; struct SYMBOL_NODE *next; }; typedef struct { UCHAR order; #ifdef _1_ unsigned long time; /* Instante de la ’ultima actualizaci’on */ #endif struct SYMBOL_NODE *list; } CONTEXT_TABLE; CONTEXT_TABLE *hash_table[65537][32]; /* En esta tabla se indica si un s’imbolo ya ha sido visitado en contextos de orden superior durante la b’usqueda del s’imbolo a codificar. As’i, se acelera el proceso de b’usqueda. */ unsigned long file_position=0; unsigned long visited[256]; #ifdef _1_ #define WINDOW_SIZE 65536 #endif #ifdef _MEMORY_INFO_ int num_contextos=0; long memory_used=0; #define MALLOC(a) malloc(a); memory_used += a; #else #define MALLOC(a) malloc(a) #endif /* Inicializa la tabla del contexto de orden 0 */ void initialize_0_order_context_table() { int i; struct SYMBOL_NODE *node; hash_table[0][0]=MALLOC(sizeof(CONTEXT_TABLE)); hash_table[0][0]->order=0; node=hash_table[0][0]->list=MALLOC(sizeof(struct SYMBOL_NODE)); for(i=1;i<256;i++) { node->next=MALLOC(sizeof(struct SYMBOL_NODE)); node=node->next; node->symbol=(unsigned char)(i/*+32*/); node->count=0; } } #ifdef _1_ void free_list(struct SYMBOL_NODE *s) { struct SYMBOL_NODE *old; while(s!=NULL) { old=s; s=s->next; free(old); } } #endif inline void create_context(int table, int pos, char *context, UCHAR order) { hash_table[table][pos]=MALLOC(sizeof(CONTEXT_TABLE)+order); strncpy((char *)hash_table[table][pos]+sizeof(CONTEXT_TABLE),context,order); hash_table[table][pos]->list=NULL; hash_table[table][pos]->order=order; #ifdef _1_ hash_table[table][pos]->time=file_position; #endif } /* Busca el contexto "context" de orden "order" en la tabla hash. Si lo encuentra devuelve su posici’on mediante un puntero, y sino, se crea una entrada en la tabla y la estructura CONTEXT_TABLE asociada al contexto. Ahora si el contexto no ha sido usado al menos WINDOW_SIZE s’imbolos, el contexto se libera y se reutiliza. */ CONTEXT_TABLE *locate(char *context, UCHAR order) { int table; unsigned long addr; for(table=0;table<32;table++) { int i; addr=0; for(i=0;i<order;i++) addr ^= context[i]<<(i*(table+1)); addr %= 65537; if(hash_table[addr][table]==NULL) { create_context(addr,table,context,order); /* Creamos un marco de contexto */ #ifdef _HASH_INFO_ num_contextos++; if(num_tablas<table) num_tablas=table; #endif return hash_table[addr][table]; /* Retornamos su direcci’on */ } if(hash_table[addr][table]->order!=order) { #ifdef _HASH_INFO_ num_colisiones++; #endif continue; } if(strncmp((char *)hash_table[addr][table]+sizeof(CONTEXT_TABLE),context,order)) { #ifdef _HASH_INFO_ num_colisiones++; #endif continue; } return hash_table[addr][table]; } fprintf(stderr,"tpc-ppm: tablas hash llenas\n"); exit(1); } /*********************************************************************/ /* M A N E J O D E L A S L I S T A S D E C O N T E X T O S */ /*********************************************************************/ /* Escala un contexto dividiendo todos los recuentos entre 2. */ void scale_context(CONTEXT_TABLE *p) { struct SYMBOL_NODE *s=p->list; while(s!=NULL) { s->count >>= 1; s=s->next; } } /* M’aximo recuento acumulado permitido para un s’imbolo. Este valor es importante para el nivel entr’opico de salida alcanzable debido a que controla la frecuencia de escalados en los contextos. */ #define MAX_COUNT 255 UCHAR search_update_context(UCHAR symbol, char *context, UCHAR order) { CONTEXT_TABLE *context_table=locate(context,order); UCHAR code=0; int found=0; struct SYMBOL_NODE *s,*r; if(context_table->list==NULL) { /* Lista vac’ia ? */ context_table->list=MALLOC(sizeof(struct SYMBOL_NODE)); context_table->list->symbol=symbol; context_table->list->count=0; context_table->list->next=NULL; } else { s=context_table->list; #ifdef _INFO_PREDICTION_ putc(s->symbol,fo); #endif while(s!=NULL) { if(visited[s->symbol]!=file_position) { visited[s->symbol]=file_position; if(s->symbol==symbol) { found=1; break; } code++; } r=s; s=s->next; } if(found) { if(s->count==MAX_COUNT) { #ifdef _SCALING_INFO_ fprintf(stderr,"S-%d ",order); #endif scale_context(context_table); } #ifndef _NO_COUNTS_ s->count++; #endif if(s!=context_table->list) { /* Si "s" no es el primer nodo de la lista */ struct SYMBOL_NODE *h,*g; if(r->count<=s->count) { /* Si la lista no est’a ordenada */ h=context_table->list; while(h->count>s->count) { g=h; h=h->next; } if(h!=context_table->list) { /* Si la nueva posici’on no es la cabeza */ g->next=s; } else { /* pero si s’i lo es */ context_table->list=s; } r->next=s->next; s->next=h; } } } else { /* Si el nodo no est’a en la lista, lo a~nadimos por el final */ struct SYMBOL_NODE *h,*g; s=MALLOC(sizeof(struct SYMBOL_NODE)); s->symbol=symbol; s->count=0; h=context_table->list; while(h->count) { g=h; h=h->next; if(h==NULL) break; } if(h!=context_table->list) { g->next=s; s->next=h; } else { s->next=h; context_table->list=s; } } } return code; } /* Creates a new context "c" with the symbol "symbol". */ void CreateContext(CONTEXT_TABLE *c, UCHAR symbol) { c->list=MALLOC(sizeof(struct SYMBOL_NODE)); c->list->symbol=symbol; c->list->count=0; c->list->next=NULL; } /* Update the symbol’s count of a symbol in a context table. The symbol must exits in the context table. */ void UpdateSymbol(CONTEXT_TABLE *ct, SYMBOL symbol) { struct SYMBOL_NODE *s=ct->list,*r; while(symbol!=s->symbol) {r=s; s=s->next;} /* Searching */ if(s->count==MAX_COUNT) /*ScaleContext*/scale_context(ct); s->count++; if(s!=ct->list) { /* If the updated symbol isn’t on the top of the list */ struct SYMBOL_NODE *h,*g; if(r->count<=s->count) { /* If the list isn’t sorted */ h=ct->list; /* Looking for the new position in the list */ while(h->count>s->count) { g=h; h=h->next; } if(h!=ct->list) g->next=s; else ct->list=s; r->next=s->next; s->next=h; } } } /* Add a new symbol to a context table. */ void AddSymbolToContext(CONTEXT_TABLE *ct, SYMBOL symbol) { if(ct->list==NULL) CreateContext(ct,symbol); else { /* Insert the symbol at the begin of the 0-count sub-list */ struct SYMBOL_NODE *s=ct->list,*r,*new; if(s->count) { while((s!=NULL) && (s->count)) { r=s; s=s->next; } new=MALLOC(sizeof(struct SYMBOL_NODE)); new->symbol=symbol; new->count=0; new->next=s; r->next=new; } else { new=MALLOC(sizeof(struct SYMBOL_NODE)); new->symbol=symbol; new->count=0; new->next=s; ct->list=new; } } } /* Retorna el s’imbolo colocado en la posici’on "code" en la tabla de contexto "ct". Si no se encuentra, devuelve -1. En cualquier caso, "code" se decrementa en cada b’usqueda. */ int FindSymbol(int *code, CONTEXT_TABLE *ct) { SYMBOL symbol; int found=0; struct SYMBOL_NODE *s=ct->list; while(s!=NULL) { /* loops inside of a context table */ if(visited[s->symbol]!=file_position) { if((*code)==0) {symbol=s->symbol; found=1; break;} visited[s->symbol]=file_position; (*code)--; } s=s->next; } if(found) return symbol; else return -1; } #ifdef _INFO_ char filtro(char ch) { if(ch<32) ch=’⋅’; return ch; } void print_context_table(char *context, UCHAR order) { CONTEXT_TABLE *ct=locate(context,order); struct SYMBOL_NODE *s=ct->list; int c=0; while(s!=NULL) { fprintf(stderr,"%c,%d ",filtro(s->symbol),s->count); s=s->next; if(c>8) break; c++; } } #endif int i; int symbol; /* S’imbolo a codificar */ int code; /* Error de predicci’on */ int order; /* Orden actual de predicci’on */ ORDER max_order; /* M’aximo orden permitido */ SYMBOL *context; /* Contexto actual */ void encode_stream(int argc, char *argv[]) { max_order=atoi(argv[2]); fprintf(stderr,"tpc-ppm: prediction order: %d\n",max_order); context=MALLOC(sizeof(UCHAR)*max_order); if(!context) { fprintf(stderr,"."); fprintf(stderr,"tpc-ppm: sin memoria para el vector contexto\n"); exit(1); } #ifdef _INFO_PREDICTION_ /* Output error-prediction file */ fo=fopen("error","wb"); if(!fo) { fprintf(stderr,"Unable to open \"error\"\n"); exit(-1); } #endif /* Inicializamos el vector contexto */ for(i=max_order-1;i>=0;i--) { context[i]=getchar(); putchar(context[i]); file_position++; } initialize_0_order_context_table(); fprintf(stderr,"tpc-ppm: coding ...\n"); symbol=getchar(); file_position++; while(symbol!=EOF) { code=0; order=max_order; for(;;) { code += search_update_context(symbol,context,order); if(visited[symbol]==file_position) break; order--; } putchar(code); #ifdef _INFO_ { int i; fprintf(stderr,"%3d(%c) %3d %2d |",symbol,filtro(symbol),code,order); for(i=max_order-1;i>=0;i--) { fprintf(stderr,"%c",filtro(context[i])); } fprintf(stderr,"|"); for(i=0;i<=max_order;i++) { print_context_table(context,i); fprintf(stderr,"|"); } fprintf(stderr,"\n"); } #endif #ifdef _MEMORY_INFO_ { int counter; if(!(counter++%256)) { fprintf(stderr,"tpc-ppm: memoria: %ld\n",memory_used); } } #endif #ifdef _MEMORY_INFO_ { int counter; if(!(counter++%256)) { fprintf(stderr,"tpc-ppm: contextos: %d\n",num_contextos); } } #endif /* Actualizamos la cadena con el nuevo contexto */ for(i=max_order-1;i>0;i--) context[i]=context[i-1]; context[0]=symbol; symbol=getchar(); file_position++; if(!file_position) memset(visited,1,256*4); } #ifdef _HASH_INFO_ fprintf(stderr,"tpc-ppm: n’unmero de contextos: %d\n",num_contextos); fprintf(stderr,"pmi: N’umero de tablas hash usadas: %d\n",num_tablas); fprintf(stderr,"pmi: N’umero de colisiones: %d\n",num_colisiones); #endif #ifdef _MEMORY_INFO_ fprintf(stderr,"tpc-ppc: memoria consumida: %ld bytes\n",memory_used); #endif fprintf(stderr,"tpc-ppm: done\n"); } void decode_stream(int argc, char *argv[]) { max_order=atoi(argv[2]); fprintf(stderr,"tpc-ppm: prediction order: %d\n",max_order); context=MALLOC(sizeof(UCHAR)*max_order); if(!context) { fprintf(stderr,"."); fprintf(stderr,"tpc-ppm: sin memoria para el vector contexto\n"); exit(1); } /* Inicializamos el vector contexto */ for(i=max_order-1;i>=0;i--) { context[i]=getchar(); putchar(context[i]); file_position++; } initialize_0_order_context_table(); fprintf(stderr,"tpc-ppm: decoding ...\n"); code=getchar(); file_position++; while(code!=EOF) { order=max_order; for(;;) { CONTEXT_TABLE *ct=locate(context,order); symbol=FindSymbol(&code,ct); if(symbol!=-1) { UpdateSymbol(ct,symbol); break; } order--; } for(i=order+1;i<=max_order;i++) { CONTEXT_TABLE *ct=locate(context,i); AddSymbolToContext(ct,symbol); } putchar(symbol); #ifdef _INFO_ { int i; fprintf(stderr,"%3d(%c) %3d %2d |",symbol,filtro(symbol),code,order); for(i=0;i<=max_order;i++) { print_context_table(context,i); fprintf(stderr,"|"); } for(i=max_order-1;i>=0;i--) { fprintf(stderr,"%c",filtro(context[i])); } fprintf(stderr,"|\n"); } #endif /* Actualizamos la cadena con el nuevo contexto */ for(i=max_order-1;i>0;i--) context[i]=context[i-1]; context[0]=symbol; code=getchar(); file_position++; if(!file_position) memset(visited,1,256*4); } #ifdef _HASH_INFO_ fprintf(stderr,"tpc-ppm: n’unmero de contextos: %d\n",num_contextos); fprintf(stderr,"pmi: N’umero de tablas hash usadas: %d\n",num_tablas); fprintf(stderr,"pmi: N’umero de colisiones: %d\n",num_colisiones); #endif #ifdef _MEMORY_INFO_ fprintf(stderr,"tpc-ppc: memoria consumida: %ld bytes\n",memory_used); #endif fprintf(stderr,"tpc-ppm: done\n"); } |