/** * @file video.c * @brief Cin file creator */ /* Copyright (C) 1997-2001 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "qdata.h" /* #define DUMPCHUNKS */ byte *soundtrack; char base[32]; /* =============================================================================== WAV loading =============================================================================== */ typedef struct { int rate; int width; int channels; int loopstart; int samples; int dataofs; /* chunk starts this many bytes from file start */ } wavinfo_t; byte *data_p; byte *iff_end; byte *last_chunk; byte *iff_data; int iff_chunk_len; int samplecounts[0x10000]; static wavinfo_t wavinfo; /** * @brief */ static short GetLittleShort(void) { short val = 0; val = *data_p; val = val + (*(data_p+1)<<8); data_p += 2; return val; } /** * @brief */ static int GetLittleLong(void) { int val = 0; val = *data_p; val = val + (*(data_p+1)<<8); val = val + (*(data_p+2)<<16); val = val + (*(data_p+3)<<24); data_p += 4; return val; } /** * @brief */ static void FindNextChunk(char *name) { while (1) { data_p=last_chunk; if (data_p >= iff_end) { /* didn't find the chunk */ data_p = NULL; return; } data_p += 4; iff_chunk_len = GetLittleLong(); if (iff_chunk_len < 0) { data_p = NULL; return; } /* if (iff_chunk_len > 1024*1024) */ /* Sys_Error("FindNextChunk: %i length is past the 1 meg sanity limit", iff_chunk_len); */ data_p -= 8; last_chunk = data_p + 8 + ( (iff_chunk_len + 1) & ~1 ); if (!strncmp((char*)data_p, name, 4)) return; } } /** * @brief */ static void FindChunk(char *name) { last_chunk = iff_data; FindNextChunk (name); } #ifdef DUMPCHUNKS /** * @brief */ static void DumpChunks(void) { char str[5]; str[4] = 0; data_p=iff_data; do { memcpy (str, data_p, 4); data_p += 4; iff_chunk_len = GetLittleLong(); printf ("0x%x : %s (%d)\n", (int)(data_p - 4), str, iff_chunk_len); data_p += (iff_chunk_len + 1) & ~1; } while (data_p < iff_end); } #endif /** * @brief */ static wavinfo_t GetWavinfo (char *name, byte *wav, int wavlength) { wavinfo_t info; int i; int format; int samples; memset (&info, 0, sizeof(info)); if (!wav) return info; iff_data = wav; iff_end = wav + wavlength; /* find "RIFF" chunk */ FindChunk("RIFF"); if (!(data_p && !strncmp((char*)data_p+8, "WAVE", 4))) { printf("Missing RIFF/WAVE chunks\n"); return info; } /* get "fmt " chunk */ iff_data = data_p + 12; #ifdef DUMPCHUNKS DumpChunks(); #endif FindChunk("fmt "); if (!data_p) { printf("Missing fmt chunk\n"); return info; } data_p += 8; format = GetLittleShort(); if (format != 1) { printf("Microsoft PCM format only\n"); return info; } info.channels = GetLittleShort(); info.rate = GetLittleLong(); data_p += 4+2; info.width = GetLittleShort() / 8; /* get cue chunk */ FindChunk("cue "); if (data_p) { data_p += 32; info.loopstart = GetLittleLong(); /* Com_Printf("loopstart=%d\n", sfx->loopstart); */ /* if the next chunk is a LIST chunk, look for a cue length marker */ FindNextChunk ("LIST"); if (data_p) { if (!strncmp ((char*)data_p + 28, "mark", 4)) { /* this is not a proper parse, but it works with cooledit... */ data_p += 24; i = GetLittleLong (); /* samples in loop */ info.samples = info.loopstart + i; } } } else info.loopstart = -1; /* find data chunk */ FindChunk("data"); if (!data_p) { printf("Missing data chunk\n"); return info; } data_p += 4; samples = GetLittleLong (); if (info.samples) { if (samples < info.samples) Error ("Sound %s has a bad loop length", name); } else info.samples = samples; info.dataofs = data_p - wav; return info; } /** * @brief */ void LoadSoundtrack (void) { char name[1024]; FILE *f; int len; int i, val, j; soundtrack = NULL; sprintf (name, "%svideo/%s/%s.wav", gamedir, base, base); printf ("%s\n", name); f = fopen (name, "rb"); if (!f) { printf ("no soundtrack for %s\n", base); return; } len = Q_filelength(f); soundtrack = malloc(len); if (fread (soundtrack, 1, len, f) != 1) printf("LoadSoundtrack: Warning, size mismatch\n"); fclose (f); wavinfo = GetWavinfo (name, soundtrack, len); /* count samples for compression */ memset (samplecounts, 0, sizeof(samplecounts)); j = wavinfo.samples/2; for (i=0 ; i wavinfo.samples || !soundtrack) { if (fwrite (&empty, 1, width, output) != width) printf("WriteSound: Write error\n"); } else { if (fwrite (soundtrack + wavinfo.dataofs + sample*width, 1, width,output) != width) printf("WriteSound: Write error\n"); } } } /** * @brief */ cblock_t MTF (cblock_t in) { int i, j, b, code; byte *out_p; int index[256]; cblock_t out; out_p = out.data = malloc(in.count + 4); /* write count */ *out_p++ = in.count&255; *out_p++ = (in.count>>8)&255; *out_p++ = (in.count>>16)&255; *out_p++ = (in.count>>24)&255; for (i=0 ; i<256 ; i++) index[i] = i; for (i=0 ; i b2) return 1; if (++i1 == bwt_size) i1 = 0; if (++i2 == bwt_size) i2 = 0; } return 0; } /** * @brief */ cblock_t BWT (cblock_t in) { int *sorted; int i; byte *out_p; cblock_t out; bwt_size = in.count; bwt_data = in.data; sorted = malloc(in.count*sizeof(*sorted)); for (i=0 ; i>8)&255; *out_p++ = (in.count>>16)&255; *out_p++ = (in.count>>24)&255; /* write head index */ for (i=0 ; i>8)&255; *out_p++ = (i>>16)&255; *out_p++ = (i>>24)&255; /* write the L column */ for (i=0 ; i 32) Error ("bitcount > 32"); charbits[nodenum] = bits; charbitscount[nodenum] = bitcount; return; } node = &hnodes[nodenum]; bits <<= 1; BuildChars (node->children[0], bits, bitcount+1); bits |= 1; BuildChars (node->children[1], bits, bitcount+1); } /** * @brief */ cblock_t Huffman (cblock_t in) { int i; hnode_t *node; int outbits, c; unsigned bits; byte *out_p; cblock_t out; int max, maxchar; /* count */ memset (hnodes, 0, sizeof(hnodes)); for (i=0 ; i max) { max = hnodes[i].count; maxchar = i; } } if (max == 0) Error ("Huffman: max == 0"); for (i=0 ; i<256 ; i++) { hnodes[i].count = (hnodes[i].count*255+max-1) / max; } /* build the nodes */ numhnodes = 256; while (numhnodes != 511) { node = &hnodes[numhnodes]; /* pick two lowest counts */ node->children[0] = SmallestNode (); if (node->children[0] == -1) break; /* no more */ node->children[1] = SmallestNode (); if (node->children[1] == -1) { if (node->children[0] != numhnodes-1) Error ("Bad smallestnode"); break; } node->count = hnodes[node->children[0]].count + hnodes[node->children[1]].count; numhnodes++; } BuildChars (numhnodes-1, 0, 0); out_p = out.data = malloc(in.count*2 + 1024); memset (out_p, 0, in.count*2+1024); /* write count */ *out_p++ = in.count&255; *out_p++ = (in.count>>8)&255; *out_p++ = (in.count>>16)&255; *out_p++ = (in.count>>24)&255; /* save out the 256 normalized counts so the tree can be recreated */ for (i=0 ; i<256 ; i++) *out_p++ = hnodes[i].count; /* write bits */ outbits = 0; for (i=0 ; i>3] |= 1<<(outbits&7); outbits++; } } out_p += (outbits+7)>>3; out.count = out_p - out.data; return out; } #if 0 #define RLE_CODE 0xe8 #define RLE_TRIPPLE 0xe9 static int rle_counts[256]; static int rle_bytes[256]; /** * @brief */ static cblock_t RLE (cblock_t in) { int i; byte *out_p; int val; int repeat; cblock_t out; out_p = out.data = malloc (in.count*2); /* write count */ *out_p++ = in.count&255; *out_p++ = (in.count>>8)&255; *out_p++ = (in.count>>16)&255; *out_p++ = (in.count>>24)&255; for (i=0 ; i 3 || val == RLE_CODE) { *out_p++ = RLE_CODE; *out_p++ = val; *out_p++ = repeat; } else { while (repeat--) *out_p++ = val; } } out.count = out_p - out.data; return out; } #endif #define MIN_REPT 15 #define MAX_REPT 0 #define HUF_TOKENS (256+MAX_REPT) static unsigned charbits1[256][HUF_TOKENS]; static int charbitscount1[256][HUF_TOKENS]; static hnode_t hnodes1[256][HUF_TOKENS*2]; static int numhnodes1[256]; static int order0counts[256]; /** * @brief */ static int SmallestNode1 (hnode_t *hnodes, int numhnodes) { int i; int best, bestnode; best = 99999999; bestnode = -1; for (i=0 ; i 32) Error ("bitcount > 32"); charbits1[prev][nodenum] = bits; charbitscount1[prev][nodenum] = bitcount; return; } node = &hnodes1[prev][nodenum]; bits <<= 1; BuildChars1 (prev, node->children[0], bits, bitcount+1); bits |= 1; BuildChars1 (prev, node->children[1], bits, bitcount+1); } /** * @brief */ static void BuildTree1 (int prev) { hnode_t *node, *nodebase; int numhnodes; /* build the nodes */ numhnodes = HUF_TOKENS; nodebase = hnodes1[prev]; while (1) { node = &nodebase[numhnodes]; /* pick two lowest counts */ node->children[0] = SmallestNode1 (nodebase, numhnodes); if (node->children[0] == -1) break; /* no more */ node->children[1] = SmallestNode1 (nodebase, numhnodes); if (node->children[1] == -1) break; node->count = nodebase[node->children[0]].count + nodebase[node->children[1]].count; numhnodes++; } numhnodes1[prev] = numhnodes-1; BuildChars1 (prev, numhnodes-1, 0, 0); } /** * @brief */ static void Huffman1_Count (cblock_t in) { int i; int prev; int v; int rept; prev = 0; for (i=0 ; i MIN_REPT) { hnodes1[prev][255+rept].count++; i += rept-1; } #endif } } static byte scaled[256][HUF_TOKENS]; /** * @brief */ static void Huffman1_Build (FILE *f) { int i, j, v; int max; int total; for (i=0 ; i<256 ; i++) { /* normalize and save the counts */ max = 0; for (j=0 ; j max) max = hnodes1[i][j].count; } if (max == 0) max = 1; total = 0; for (j=0 ; j 255) Error ("v > 255"); scaled[i][j] = hnodes1[i][j].count = v; if (v) total++; } if (total == 1) { /* must have two tokens */ if (!scaled[i][0]) scaled[i][0] = hnodes1[i][0].count = 1; else scaled[i][1] = hnodes1[i][1].count = 1; } BuildTree1 (i); } #if 0 /* count up the total bits */ total = 0; for (i=0 ; i<256 ; i++) for (j=0 ; j<256 ; j++) total += charbitscount1[i][j] * hnodes1[i][j].count; total = (total+7)/8; printf ("%i bytes huffman1 compressed\n", total); #endif if (fwrite (scaled, 1, sizeof(scaled), f) != sizeof(scaled)) printf("Huffman1_Build: Write error\n"); } /** * @brief Order 1 compression with pre-built table */ static cblock_t Huffman1 (cblock_t in) { int i; int outbits, c; unsigned bits; byte *out_p; cblock_t out; int prev; int v; int rept; out_p = out.data = malloc(in.count*2 + 1024); memset (out_p, 0, in.count*2+1024); /* write count */ *out_p++ = in.count&255; *out_p++ = (in.count>>8)&255; *out_p++ = (in.count>>16)&255; *out_p++ = (in.count>>24)&255; /* write bits */ outbits = 0; prev = 0; for (i=0 ; i>3] |= 1<<(outbits&7); outbits++; } prev = v; #if 1 /* check for repeat encodes */ for (rept=1 ; i+rept < in.count && rept < MAX_REPT ; rept++) if (in.data[i+rept] != v) break; if (rept > MIN_REPT) { c = charbitscount1[prev][255+rept]; bits = charbits1[prev][255+rept]; if (!c) Error ("!bits"); while (c) { c--; if (bits & (1<>3] |= 1<<(outbits&7); outbits++; } i += rept-1; } #endif } out_p += (outbits+7)>>3; out.count = out_p - out.data; return out; } /** * @brief */ static cblock_t LoadFrame (char *base, int frame, int digits, byte **palette) { int ten3, ten2, ten1, ten0; cblock_t in; int width, height; char name[1024]; FILE *f; in.data = NULL; in.count = -1; ten3 = frame/1000; ten2 = (frame-ten3*1000)/100; ten1 = (frame-ten3*1000-ten2*100)/10; ten0 = frame%10; if (digits == 4) sprintf (name, "%svideo/%s/%s%i%i%i%i.pcx", gamedir, base, base, ten3, ten2, ten1, ten0); else sprintf (name, "%svideo/%s/%s%i%i%i.pcx", gamedir, base, base, ten2, ten1, ten0); f = fopen(name, "rb"); if (!f) { in.data = NULL; return in; } fclose (f); printf ("%s\n", name); Load256Image (name, &in.data, palette, &width, &height); in.count = width*height; /* FIXME: map 0 and 255! */ #if 0 /* rle compress */ rle = RLE(in); free (in.data); return rle; #endif return in; } /** * @brief video */ extern void Cmd_Video (void) { char savename[1024]; char name[1024]; FILE *output; int startframe, frame; byte *palette; int width, height; byte current_palette[768]; int command; int i; int digits; cblock_t in, huffman; int swap; GetToken (qfalse); strcpy (base, token); if (g_release) { /* sprintf (savename, "video/%s.cin", token); */ /* ReleaseFile (savename); */ return; } GetToken (qfalse); digits = atoi(token); /* optionally skip frames */ if (TokenAvailable ()) { GetToken (qfalse); startframe = atoi(token); } else startframe=0; sprintf (savename, "%svideo/%s.cin", gamedir, base); /* clear stuff */ memset (charbits1, 0, sizeof(charbits1)); memset (charbitscount1, 0, sizeof(charbitscount1)); memset (hnodes1, 0, sizeof(hnodes1)); memset (numhnodes1, 0, sizeof(numhnodes1)); memset (order0counts, 0, sizeof(order0counts)); /* load the entire sound wav file if present */ LoadSoundtrack (); if (digits == 4) sprintf (name, "%svideo/%s/%s0000.pcx", gamedir, base, base); else sprintf (name, "%svideo/%s/%s000.pcx", gamedir, base, base); printf ("%s\n", name); Load256Image (name, NULL, &palette, &width, &height); output = fopen (savename, "wb"); if (!output) Error ("Can't open %s", savename); /* write header info */ i = LittleLong (width); if (fwrite (&i, 4, 1, output) != 1) printf("Cmd_Video: Write error\n"); i = LittleLong (height); if (fwrite (&i, 4, 1, output) != 1) printf("Cmd_Video: Write error\n"); i = LittleLong (wavinfo.rate); if (fwrite (&i, 4, 1, output) != 1) printf("Cmd_Video: Write error\n"); i = LittleLong (wavinfo.width); if (fwrite (&i, 4, 1, output) != 1) printf("Cmd_Video: Write error\n"); i = LittleLong (wavinfo.channels); if (fwrite (&i, 4, 1, output) != 1) printf("Cmd_Video: Write error\n"); /* build the dictionary */ for ( frame=startframe ; ; frame++) { printf ("counting %i ", frame); in = LoadFrame (base, frame, digits, &palette); if (!in.data) break; Huffman1_Count (in); free (in.data); } printf ("\n"); /* build nodes and write counts */ Huffman1_Build (output); memset (current_palette, 0, sizeof(current_palette)); /* compress it with the dictionary */ for (frame=startframe ; ; frame++) { printf ("packing %i ", frame); in = LoadFrame (base, frame, digits, &palette); if (!in.data) break; /* see if the palette has changed */ for (i=0 ; i<768 ; i++) if (palette[i] != current_palette[i]) { /* write a palette change */ memcpy (current_palette, palette, sizeof(current_palette)); command = LittleLong(1); if (fwrite (&command, 1, 4, output) != 4) printf("Cmd_Video: Write error\n"); if (fwrite (current_palette, 1, sizeof(current_palette), output) != sizeof(current_palette)) printf("Cmd_Video: Write error\n"); break; } if (i == 768) { command = 0; /* no palette change */ if (fwrite (&command, 1, 4, output) != 4) printf("Cmd_Video: Write error\n"); } /* save the image */ huffman = Huffman1 (in); printf ("%5i bytes after huffman1\n", huffman.count); swap = LittleLong (huffman.count); if (fwrite (&swap, 1, sizeof(swap), output) != sizeof(swap)) printf("Cmd_Video: Write error\n"); if (fwrite (huffman.data, 1, huffman.count, output) != huffman.count) printf("Cmd_Video: Write error\n"); /* save some sound samples */ WriteSound (output, frame); free (palette); free (in.data); free (huffman.data); } printf ("\n"); /* write end-of-file command */ command = 2; if (fwrite (&command, 1, 4, output) != 4) printf("Cmd_Video: Write error\n"); printf ("Total size: %li\n", ftell (output)); fclose (output); if (soundtrack) free (soundtrack); }