]> de.git.xonotic.org Git - xonotic/netradiant.git/blobdiff - tools/quake3/q3data/video.c
set eol-style
[xonotic/netradiant.git] / tools / quake3 / q3data / video.c
index 35e097bf3a2c70e1147378bb6f565f49b9e476c0..9054d5e90700419de03309cda1a28a98553b19ba 100644 (file)
-#include <assert.h>\r
-#include "q3data.h"\r
-\r
-static int s_resample_width = 256;\r
-static int s_resample_height = 256;\r
-\r
-#define OUTPUT_TGAS                    1\r
-\r
-#define UNCOMPRESSED           0\r
-#define BTC_COMPRESSION                1\r
-\r
-static int s_compression_method = BTC_COMPRESSION;\r
-\r
-static const char *CIN_EXTENSION = "cn2";\r
-static const int CIN_SIGNATURE = ( 'C' << 24 ) | ( 'I' << 16 ) | ( 'N' << 8 ) | ( '2' );\r
-\r
-static byte    *s_soundtrack;\r
-static char    s_base[32];\r
-static char    s_output_base[32];\r
-\r
-/*\r
-===============================================================================\r
-\r
-WAV loading\r
-\r
-===============================================================================\r
-*/\r
-\r
-typedef struct\r
-{\r
-       int                     rate;\r
-       int                     width;\r
-       int                     channels;\r
-       int                     loopstart;\r
-       int                     samples;\r
-       int                     dataofs;                // chunk starts this many bytes from file start\r
-} wavinfo_t;\r
-\r
-\r
-byte   *data_p;\r
-byte   *iff_end;\r
-byte   *last_chunk;\r
-byte   *iff_data;\r
-int    iff_chunk_len;\r
-\r
-\r
-static int                     s_samplecounts[0x10000];\r
-static wavinfo_t       s_wavinfo;\r
-\r
-short GetLittleShort(void)\r
-{\r
-       short val = 0;\r
-       val = *data_p;\r
-       val = val + (*(data_p+1)<<8);\r
-       data_p += 2;\r
-       return val;\r
-}\r
-\r
-int GetLittleLong(void)\r
-{\r
-       int val = 0;\r
-       val = *data_p;\r
-       val = val + (*(data_p+1)<<8);\r
-       val = val + (*(data_p+2)<<16);\r
-       val = val + (*(data_p+3)<<24);\r
-       data_p += 4;\r
-       return val;\r
-}\r
-\r
-void FindNextChunk(char *name)\r
-{\r
-       while (1)\r
-       {\r
-               data_p=last_chunk;\r
-\r
-               if (data_p >= iff_end)\r
-               {       // didn't find the chunk\r
-                       data_p = NULL;\r
-                       return;\r
-               }\r
-               \r
-               data_p += 4;\r
-               iff_chunk_len = GetLittleLong();\r
-               if (iff_chunk_len < 0)\r
-               {\r
-                       data_p = NULL;\r
-                       return;\r
-               }\r
-//             if (iff_chunk_len > 1024*1024)\r
-//                     Sys_Error ("FindNextChunk: %i length is past the 1 meg sanity limit", iff_chunk_len);\r
-               data_p -= 8;\r
-               last_chunk = data_p + 8 + ( (iff_chunk_len + 1) & ~1 );\r
-               if (!strncmp(data_p, name, 4))\r
-                       return;\r
-       }\r
-}\r
-\r
-void FindChunk(char *name)\r
-{\r
-       last_chunk = iff_data;\r
-       FindNextChunk (name);\r
-}\r
-\r
-\r
-void DumpChunks(void)\r
-{\r
-       char    str[5];\r
-       \r
-       str[4] = 0;\r
-       data_p=iff_data;\r
-       do\r
-       {\r
-               memcpy (str, data_p, 4);\r
-               data_p += 4;\r
-               iff_chunk_len = GetLittleLong();\r
-               printf ("0x%x : %s (%d)\n", (int)(data_p - 4), str, iff_chunk_len);\r
-               data_p += (iff_chunk_len + 1) & ~1;\r
-       } while (data_p < iff_end);\r
-}\r
-\r
-/*\r
-============\r
-GetWavinfo\r
-============\r
-*/\r
-wavinfo_t GetWavinfo (char *name, byte *wav, int wavlength)\r
-{\r
-       wavinfo_t       info;\r
-       int     i;\r
-       int     format;\r
-       int             samples;\r
-\r
-       memset (&info, 0, sizeof(info));\r
-\r
-       if (!wav)\r
-               return info;\r
-               \r
-       iff_data = wav;\r
-       iff_end = wav + wavlength;\r
-\r
-// find "RIFF" chunk\r
-       FindChunk("RIFF");\r
-       if (!(data_p && !strncmp(data_p+8, "WAVE", 4)))\r
-       {\r
-               printf("Missing RIFF/WAVE chunks\n");\r
-               return info;\r
-       }\r
-\r
-// get "fmt " chunk\r
-       iff_data = data_p + 12;\r
-// DumpChunks ();\r
-\r
-       FindChunk("fmt ");\r
-       if (!data_p)\r
-       {\r
-               printf("Missing fmt chunk\n");\r
-               return info;\r
-       }\r
-       data_p += 8;\r
-       format = GetLittleShort();\r
-       if (format != 1)\r
-       {\r
-               printf("Microsoft PCM format only\n");\r
-               return info;\r
-       }\r
-\r
-       info.channels = GetLittleShort();\r
-       info.rate = GetLittleLong();\r
-       data_p += 4+2;\r
-       info.width = GetLittleShort() / 8;\r
-\r
-// get cue chunk\r
-       FindChunk("cue ");\r
-       if (data_p)\r
-       {\r
-               data_p += 32;\r
-               info.loopstart = GetLittleLong();\r
-//             Com_Printf("loopstart=%d\n", sfx->loopstart);\r
-\r
-       // if the next chunk is a LIST chunk, look for a cue length marker\r
-               FindNextChunk ("LIST");\r
-               if (data_p)\r
-               {\r
-                       if (!strncmp (data_p + 28, "mark", 4))\r
-                       {       // this is not a proper parse, but it works with cooledit...\r
-                               data_p += 24;\r
-                               i = GetLittleLong ();   // samples in loop\r
-                               info.samples = info.loopstart + i;\r
-                       }\r
-               }\r
-       }\r
-       else\r
-               info.loopstart = -1;\r
-\r
-// find data chunk\r
-       FindChunk("data");\r
-       if (!data_p)\r
-       {\r
-               printf("Missing data chunk\n");\r
-               return info;\r
-       }\r
-\r
-       data_p += 4;\r
-       samples = GetLittleLong ();\r
-\r
-       if (info.samples)\r
-       {\r
-               if (samples < info.samples)\r
-                       Error ("Sound %s has a bad loop length", name);\r
-       }\r
-       else\r
-               info.samples = samples;\r
-\r
-       info.dataofs = data_p - wav;\r
-\r
-       return info;\r
-}\r
-\r
-//=====================================================================\r
-\r
-/*\r
-==============\r
-LoadSoundtrack\r
-==============\r
-*/\r
-void LoadSoundtrack (void)\r
-{\r
-       char    name[1024];\r
-       FILE    *f;\r
-       int             len;\r
-       int     i, val, j;\r
-\r
-       s_soundtrack = NULL;\r
-       sprintf (name, "%svideo/%s/%s.wav", gamedir, s_base, s_base);\r
-       printf ("WAV: %s\n", name);\r
-       f = fopen (name, "rb");\r
-       if (!f)\r
-       {\r
-               printf ("no soundtrack for %s\n", s_base);\r
-               return;\r
-       }\r
-       len = Q_filelength(f);\r
-       s_soundtrack = malloc(len);\r
-       fread (s_soundtrack, 1, len, f);\r
-       fclose (f);\r
-\r
-       s_wavinfo = GetWavinfo (name, s_soundtrack, len);\r
-\r
-       // count samples for compression\r
-       memset (s_samplecounts, 0, sizeof(s_samplecounts));\r
-\r
-       j = s_wavinfo.samples/2;\r
-       for (i=0 ; i<j ; i++)\r
-       {\r
-               val = ((unsigned short *)( s_soundtrack + s_wavinfo.dataofs))[i];\r
-               s_samplecounts[val]++;\r
-       }\r
-       val = 0;\r
-       for (i=0 ; i<0x10000 ; i++)\r
-               if (s_samplecounts[i])\r
-                       val++;\r
-\r
-       printf ("%i unique sample values\n", val);\r
-}\r
-\r
-/*\r
-==================\r
-WriteSound\r
-==================\r
-*/\r
-void WriteSound (FILE *output, int frame)\r
-{\r
-       int             start, end;\r
-       int             count;\r
-       int             empty = 0;\r
-       int             i;\r
-       int             sample;\r
-       int             width;\r
-\r
-       width = s_wavinfo.width * s_wavinfo.channels;\r
-\r
-       start = frame*s_wavinfo.rate/14;\r
-       end = (frame+1)*s_wavinfo.rate/14;\r
-       count = end - start;\r
-\r
-       for (i=0 ; i<count ; i++)\r
-       {\r
-               sample = start+i;\r
-               if (sample > s_wavinfo.samples || !s_soundtrack)\r
-                       fwrite (&empty, 1, width, output);\r
-               else\r
-                       fwrite (s_soundtrack + s_wavinfo.dataofs + sample*width, 1, width,output);\r
-       }\r
-}\r
-\r
-//==========================================================================\r
-\r
-static float s_resampleXRatio;\r
-static float s_resampleYRatio;\r
-\r
-static void BoxFilterHorizontalElements( unsigned char *dst, unsigned char *src, float s0, float s1 )\r
-{\r
-       float w;\r
-       float rSum = 0, gSum = 0, bSum = 0;\r
-       float x = s0;\r
-       float sumWeight = 0;\r
-\r
-       for ( x = s0; x < s1; x++, src += 4 )\r
-       {\r
-               if ( x == s0 )\r
-               {\r
-                       w = ( int ) ( s0 + 1 ) - x;\r
-               }\r
-               else if ( x + 1 >= s1 )\r
-               {\r
-                       w = s1 - ( int ) x;\r
-               }\r
-               else\r
-               {\r
-                       w = 1.0f;\r
-               }\r
-\r
-               rSum += src[0] * w;\r
-               gSum += src[1] * w;\r
-               bSum += src[2] * w;\r
-               sumWeight += w;\r
-       }\r
-\r
-       rSum /= sumWeight;\r
-       gSum /= sumWeight;\r
-       bSum /= sumWeight;\r
-\r
-       dst[0] = ( unsigned char ) ( rSum + 0.5 );\r
-       dst[1] = ( unsigned char ) ( gSum + 0.5 );\r
-       dst[2] = ( unsigned char ) ( bSum + 0.5 );\r
-}\r
-\r
-static void BoxFilterVerticalElements( unsigned char *dst, // destination of the filter process\r
-                                                                          unsigned char *src, // source pixels\r
-                                                                          int srcStep,            // stride of the source pixels\r
-                                                                          float s0, float s1 )\r
-{\r
-       float w;\r
-       float rSum = 0, gSum = 0, bSum = 0;\r
-       float y = s0;\r
-       float sumWeight = 0;\r
-\r
-       for ( y = s0; y < ( int ) ( s1 + 1 ) ; y++, src += srcStep )\r
-       {\r
-               if ( y == s0 )\r
-               {\r
-                       w = ( int ) ( s0 + 1 ) - y;\r
-               }\r
-               else if ( y + 1 >= s1 )\r
-               {\r
-                       w = s1 - ( int ) y;\r
-               }\r
-               else\r
-               {\r
-                       w = 1.0f;\r
-               }\r
-\r
-               rSum += src[0] * w;\r
-               gSum += src[1] * w;\r
-               bSum += src[2] * w;\r
-               sumWeight += w;\r
-       }\r
-\r
-       rSum /= sumWeight;\r
-       gSum /= sumWeight;\r
-       bSum /= sumWeight;\r
-\r
-       dst[0] = ( unsigned char ) ( rSum + 0.5 );\r
-       dst[1] = ( unsigned char ) ( gSum + 0.5 );\r
-       dst[2] = ( unsigned char ) ( bSum + 0.5 );\r
-       dst[3] = 0xff;\r
-\r
-}\r
-\r
-static void BoxFilterRow( unsigned char *dstStart, cblock_t *in, int dstRow, int rowWidth )\r
-{\r
-       int i;\r
-       unsigned char *indata = ( unsigned char * ) in->data;\r
-\r
-       indata += 4 * dstRow * in->width;\r
-\r
-       for ( i = 0; i < rowWidth; i++ )\r
-       {\r
-               float c0 = i * s_resampleXRatio;\r
-               float c1 = ( i + 1 ) * s_resampleXRatio;\r
-\r
-               BoxFilterHorizontalElements( &dstStart[i*4], &indata[( ( int ) c0 ) * 4], c0, c1 );\r
-       }\r
-}\r
-\r
-static void BoxFilterColumn( unsigned char *dstStart, unsigned char *srcStart, int dstCol, int dstRowWidth, int dstColHeight, int srcRowWidthInPels )\r
-{\r
-       float c0, c1;\r
-       int i;\r
-\r
-       for ( i = 0; i < dstColHeight; i++ )\r
-       {\r
-               c0 = i * s_resampleYRatio;\r
-               c1 = ( i + 1 ) * s_resampleYRatio;\r
-\r
-               BoxFilterVerticalElements( &dstStart[i*4*dstRowWidth], &srcStart[(int)c0*srcRowWidthInPels*4], srcRowWidthInPels*4, c0, c1 );\r
-       }\r
-}\r
-\r
-#define DROP_SAMPLE            0\r
-#define BOX_FILTER             1\r
-\r
-static void ResampleFrame( cblock_t *in, unsigned char *out, int method, int outWidth, int outHeight )\r
-{\r
-       int row, column;\r
-       unsigned char *indata = ( unsigned char * ) in->data;\r
-\r
-       s_resampleXRatio = in->width / ( float ) outWidth;\r
-       s_resampleYRatio = in->height / ( float ) outHeight;\r
-\r
-       if ( method == DROP_SAMPLE )\r
-       {\r
-               for ( row = 0; row < outHeight; row++ )\r
-               {\r
-                       int r = ( int ) ( row * s_resampleYRatio );\r
-\r
-                       for ( column = 0; column < outWidth; column++ )\r
-                       {\r
-                               int c = ( int ) ( column * s_resampleXRatio );\r
-\r
-                               out[(row*outWidth+column)*4+0] = indata[(r*in->width+c)*4+0];\r
-                               out[(row*outWidth+column)*4+1] = indata[(r*in->width+c)*4+1];\r
-                               out[(row*outWidth+column)*4+2] = indata[(r*in->width+c)*4+2];\r
-                               out[(row*outWidth+column)*4+3] = 0xff;\r
-                       }\r
-               }\r
-       }\r
-       else if ( method == BOX_FILTER )\r
-       {\r
-               unsigned char intermediate[1024*1024*4];\r
-\r
-               assert( in->height <= 1024 );\r
-               assert( in->width <= 1024 );\r
-\r
-               //\r
-               // filter our M x N source image into a RESAMPLE_WIDTH x N horizontally filtered image\r
-               //\r
-               for ( row = 0; row < in->height; row++ )\r
-               {\r
-                       BoxFilterRow( &intermediate[row*4*outWidth], in, row, outWidth );\r
-               }\r
-\r
-               //\r
-               // filter our RESAMPLE_WIDTH x N horizontally filtered image into a RESAMPLE_WIDTH x RESAMPLE_HEIGHT filtered image\r
-               //\r
-               for ( column = 0; column < outWidth; column++ )\r
-               {\r
-                       BoxFilterColumn( &out[column*4], &intermediate[column*4], column, outWidth, outHeight, s_resample_width );\r
-               }\r
-       }\r
-}\r
-\r
-static float BTCDistanceSquared( float a[3], float b[3] )\r
-{\r
-       return ( b[0] - a[0] ) * ( b[0] - a[0] ) + \r
-                  ( b[1] - a[1] ) * ( b[1] - a[1] ) +\r
-                  ( b[2] - a[2] ) * ( b[2] - a[2] );\r
-}\r
-\r
-static void BTCFindEndpoints( float inBlock[4][4][3], unsigned int endPoints[2][2] )\r
-{\r
-       float longestDistance = -1;\r
-\r
-       int bX, bY;\r
-\r
-       //\r
-       // find the two points farthest from each other\r
-       //\r
-       for ( bY = 0; bY < 4; bY++ )\r
-       {\r
-               for ( bX = 0; bX < 4; bX++ )\r
-               {\r
-                       int cX, cY;\r
-                       float d;\r
-\r
-                       //\r
-                       // check the rest of the current row\r
-                       //\r
-                       for ( cX = bX + 1; cX < 4; cX++ )\r
-                       {\r
-                               if ( ( d = BTCDistanceSquared( inBlock[bY][bX], inBlock[bY][cX] ) ) > longestDistance )\r
-                               {\r
-                                       longestDistance = d;\r
-                                       endPoints[0][0] = bX;\r
-                                       endPoints[0][1] = bY;\r
-                                       endPoints[1][0] = cX;\r
-                                       endPoints[1][1] = bY;\r
-                               }\r
-                       }\r
-\r
-                       //\r
-                       // check remaining rows and columns\r
-                       //\r
-                       for ( cY = bY+1; cY < 4; cY++ )\r
-                       {\r
-                               for ( cX = 0; cX < 4; cX++ )\r
-                               {\r
-                                       if ( ( d = BTCDistanceSquared( inBlock[bY][bX], inBlock[cY][cX] ) ) > longestDistance )\r
-                                       {\r
-                                               longestDistance = d;\r
-                                               endPoints[0][0] = bX;\r
-                                               endPoints[0][1] = bY;\r
-                                               endPoints[1][0] = cX;\r
-                                               endPoints[1][1] = cY;\r
-                                       }\r
-                               }\r
-                       }\r
-               }\r
-       }\r
-}\r
-\r
-static float BTCQuantizeBlock( float inBlock[4][4][3], unsigned long endPoints[2][2], int btcQuantizedBlock[4][4], float bestError )\r
-{\r
-       int i;\r
-       int blockY, blockX;\r
-       float dR, dG, dB;\r
-       float R, G, B;\r
-       float error = 0;\r
-       float colorLine[4][3];\r
-\r
-       //\r
-       // build the color line\r
-       //\r
-       dR = inBlock[endPoints[1][1]][endPoints[1][0]][0] -\r
-                inBlock[endPoints[0][1]][endPoints[0][0]][0];\r
-       dG = inBlock[endPoints[1][1]][endPoints[1][0]][1] -\r
-                inBlock[endPoints[0][1]][endPoints[0][0]][1];\r
-       dB = inBlock[endPoints[1][1]][endPoints[1][0]][2] -\r
-                inBlock[endPoints[0][1]][endPoints[0][0]][2];\r
-\r
-       dR *= 0.33f;\r
-       dG *= 0.33f;\r
-       dB *= 0.33f;\r
-\r
-       R = inBlock[endPoints[0][1]][endPoints[0][0]][0];\r
-       G = inBlock[endPoints[0][1]][endPoints[0][0]][1];\r
-       B = inBlock[endPoints[0][1]][endPoints[0][0]][2];\r
-\r
-       for ( i = 0; i < 4; i++ )\r
-       {\r
-               colorLine[i][0] = R;\r
-               colorLine[i][1] = G;\r
-               colorLine[i][2] = B;\r
-\r
-               R += dR;\r
-               G += dG;\r
-               B += dB;\r
-       }\r
-\r
-       //\r
-       // quantize each pixel into the appropriate range\r
-       //\r
-       for ( blockY = 0; blockY < 4; blockY++ )\r
-       {\r
-               for ( blockX = 0; blockX < 4; blockX++ )\r
-               {\r
-                       float distance = 10000000000;\r
-                       int shortest = -1;\r
-\r
-                       for ( i = 0; i < 4; i++ )\r
-                       {\r
-                               float d;\r
-\r
-                               if ( ( d = BTCDistanceSquared( inBlock[blockY][blockX], colorLine[i] ) ) < distance )\r
-                               {\r
-                                       distance = d;\r
-                                       shortest = i;\r
-                               }\r
-                       }\r
-\r
-                       error += distance;\r
-\r
-                       //\r
-                       // if bestError is not -1 then that means this is a speculative quantization\r
-                       //\r
-                       if ( bestError != -1 )\r
-                       {\r
-                               if ( error > bestError )\r
-                                       return error;\r
-                       }\r
-\r
-                       btcQuantizedBlock[blockY][blockX] = shortest;\r
-               }\r
-       }\r
-\r
-       return error;\r
-}\r
-\r
-/*\r
-** float BTCCompressBlock\r
-*/\r
-static float BTCCompressBlock( float inBlock[4][4][3], unsigned long out[2] )\r
-{\r
-       int i;\r
-       int btcQuantizedBlock[4][4];    // values should be [0..3]\r
-       unsigned long encodedEndPoints, encodedBitmap;\r
-       unsigned int endPoints[2][2];           // endPoints[0] = color start, endPoints[1] = color end\r
-       int blockY, blockX;\r
-       float error = 0;\r
-       float bestError = 10000000000;\r
-       unsigned int bestEndPoints[2][2];\r
-\r
-#if 0\r
-       //\r
-       // find the "ideal" end points for the color vector \r
-       //\r
-       BTCFindEndpoints( inBlock, endPoints );\r
-       error = BTCQuantizeBlock( inBlock, endPoints, btcQuantizedBlock );\r
-       memcpy( bestEndPoints, endPoints, sizeof( bestEndPoints ) );\r
-#else\r
-       for ( blockY = 0; blockY < 4; blockY++ )\r
-       {\r
-               for ( blockX = 0; blockX < 4; blockX++ )\r
-               {\r
-                       int x2, y2;\r
-\r
-                       for ( y2 = 0; y2 < 4; y2++ )\r
-                       {\r
-                               for ( x2 = 0; x2 < 4; x2++ )\r
-                               {\r
-                                       if ( ( x2 == blockX ) && ( y2 == blockY ) )\r
-                                               continue;\r
-\r
-                                       endPoints[0][0] = blockX;\r
-                                       endPoints[0][1] = blockY;\r
-                                       endPoints[1][0] = x2;\r
-                                       endPoints[1][1] = y2;\r
-\r
-                                       error = BTCQuantizeBlock( inBlock, endPoints, btcQuantizedBlock, -1 ); //bestError );\r
-\r
-                                       if ( error < bestError )\r
-                                       {\r
-                                               bestError = error;\r
-                                               memcpy( bestEndPoints, endPoints, sizeof( bestEndPoints ) );\r
-                                       }\r
-                               }\r
-                       }\r
-               }\r
-       }\r
-\r
-       error = BTCQuantizeBlock( inBlock, bestEndPoints, btcQuantizedBlock, -1.0f );\r
-#endif\r
-\r
-       //\r
-       // encode the results\r
-       //\r
-       encodedBitmap = 0;\r
-       for ( blockY = 0; blockY < 4; blockY++ )\r
-       {\r
-               for ( blockX = 0; blockX < 4; blockX++ )\r
-               {\r
-                       int shift = ( blockX + blockY * 4 ) * 2;\r
-                       encodedBitmap |= btcQuantizedBlock[blockY][blockX] << shift;\r
-               }\r
-       }\r
-\r
-       //\r
-       // encode endpoints\r
-       //\r
-       encodedEndPoints = 0;\r
-       for ( i = 0; i < 2; i++ )\r
-       {\r
-               int iR, iG, iB;\r
-\r
-               iR = ( ( int ) inBlock[bestEndPoints[i][1]][bestEndPoints[i][0]][0] );\r
-               if ( iR > 255 ) \r
-                       iR = 255;\r
-               else if ( iR < 0 ) \r
-                       iR = 0;\r
-               iR >>= 3;\r
-\r
-               iG = ( ( int ) inBlock[bestEndPoints[i][1]][bestEndPoints[i][0]][1] );\r
-               if ( iG > 255 )\r
-                       iG = 255;\r
-               else if ( iG < 0 )\r
-                       iG = 0;\r
-               iG >>= 2;\r
-\r
-               iB = ( ( int ) inBlock[bestEndPoints[i][1]][bestEndPoints[i][0]][2] );\r
-               if ( iB > 255 )\r
-                       iB = 255;\r
-               else if ( iB < 0 )\r
-                       iB = 0;\r
-               iB >>= 3;\r
-\r
-\r
-               encodedEndPoints |= ( ( ( iR << 11 ) | ( iG << 5 ) | ( iB ) ) << ( i * 16 ) );\r
-       }\r
-\r
-       //\r
-       // store \r
-       //\r
-       out[0] = encodedBitmap;\r
-       out[1] = encodedEndPoints;\r
-\r
-       return error;\r
-}\r
-\r
-/*\r
-** void BTCDecompressFrame\r
-*/\r
-static void BTCDecompressFrame( unsigned long *src, unsigned char *dst )\r
-{\r
-       int x, y;\r
-       int iR, iG, iB;\r
-       int dstX, dstY;\r
-       float colorStart[3], colorEnd[3];\r
-       unsigned char colorRampABGR[4][4];\r
-       unsigned encoded;\r
-\r
-       memset( colorRampABGR, 0xff, sizeof( colorRampABGR ) );\r
-\r
-       for ( y = 0; y < s_resample_height / 4; y++ )\r
-       {\r
-               for ( x = 0; x < s_resample_width / 4; x++ )\r
-               {\r
-                       unsigned colorStartPacked = src[(y*s_resample_width/4 + x)*2 + 1] & 0xffff;\r
-                       unsigned colorEndPacked = src[(y*s_resample_width/4 + x)*2 + 1] >> 16;\r
-\r
-                       //\r
-                       // grab the end points\r
-                       //   0 = color start\r
-                       //   1 = color end\r
-                       //\r
-                       iR = ( ( colorStartPacked >> 11 ) & ( ( 1 << 5 ) - 1 ) );\r
-                       iR = ( iR << 3 ) | ( iR >> 2 );\r
-                       iG = ( ( colorStartPacked >> 5 ) & ( ( 1 << 6 )  - 1 ) );\r
-                       iG = ( iG << 2 ) | ( iG >> 4 );\r
-                       iB = ( ( colorStartPacked ) & ( ( 1 << 5  ) - 1 ) );\r
-                       iB = ( iB << 3 ) | ( iB >> 2 );\r
-\r
-                       colorStart[0] = iR;\r
-                       colorStart[1] = iG;\r
-                       colorStart[2] = iB;\r
-                       colorRampABGR[0][0] = iR;\r
-                       colorRampABGR[0][1] = iG;\r
-                       colorRampABGR[0][2] = iB;\r
-\r
-                       iR = ( ( colorEndPacked >> 11 ) & ( ( 1 << 5 ) - 1 ) );\r
-                       iR = ( iR << 3 ) | ( iR >> 2 );\r
-                       iG = ( ( colorEndPacked >> 5 ) & ( ( 1 << 6 )  - 1 ) );\r
-                       iG = ( iG << 2 ) | ( iG >> 4 );\r
-                       iB = ( colorEndPacked & ( ( 1 << 5  ) - 1 ) );\r
-                       iB = ( iB << 3 ) | ( iB >> 2 );\r
-\r
-                       colorEnd[0] = iR;\r
-                       colorEnd[1] = iG;\r
-                       colorEnd[2] = iB;\r
-                       colorRampABGR[3][0] = iR;\r
-                       colorRampABGR[3][1] = iG;\r
-                       colorRampABGR[3][2] = iB;\r
-                       \r
-                       //\r
-                       // compute this block's color ramp\r
-                       // FIXME: This needs to be reversed on big-endian machines\r
-                       //\r
-                       \r
-                       colorRampABGR[1][0] = colorStart[0] * 0.66f + colorEnd[0] * 0.33f;\r
-                       colorRampABGR[1][1] = colorStart[1] * 0.66f + colorEnd[1] * 0.33f;\r
-                       colorRampABGR[1][2] = colorStart[2] * 0.66f + colorEnd[2] * 0.33f;\r
-\r
-                       colorRampABGR[2][0] = colorStart[0] * 0.33f + colorEnd[0] * 0.66f;\r
-                       colorRampABGR[2][1] = colorStart[1] * 0.33f + colorEnd[1] * 0.66f;\r
-                       colorRampABGR[2][2] = colorStart[2] * 0.33f + colorEnd[2] * 0.66f;\r
-\r
-                       //\r
-                       // decode the color data\r
-                       // information is encoded in 2-bit pixels, with low order bits corresponding\r
-                       // to upper left pixels.  These 2-bit values are indexed into the block's\r
-                       // computer color ramp.\r
-                       //\r
-                       encoded = src[(y*s_resample_width/4 + x)*2 + 0];\r
-\r
-                       for ( dstY = 0; dstY < 4; dstY++ )\r
-                       {\r
-                               for ( dstX = 0; dstX < 4; dstX++ )\r
-                               {\r
-                                       memcpy( &dst[(y*4+dstY)*s_resample_width*4+x*4*4+dstX*4], colorRampABGR[encoded&3], sizeof( colorRampABGR[0] ) );\r
-                                       encoded >>= 2;\r
-                               }\r
-                       }\r
-               }\r
-       }\r
-}\r
-\r
-/*\r
-** BTCCompressFrame\r
-**\r
-** Perform a BTC compression using a 2-bit encoding at each pixel.  This\r
-** compression method is performed by decomposing the incoming image into\r
-** a sequence of 4x4 blocks.  At each block two color values are computed\r
-** that define the endpoints of a vector in color space that represent\r
-** the two colors "farthest apart".\r
-*/\r
-static float BTCCompressFrame( unsigned char *src, unsigned long *dst )\r
-{\r
-       int x, y;\r
-       int bX, bY;\r
-       float btcBlock[4][4][3];\r
-\r
-       float error = 0;\r
-       \r
-       for ( y = 0; y < s_resample_height / 4; y++ )\r
-       {\r
-               for ( x = 0; x < s_resample_width / 4; x++ )\r
-               {\r
-                       //\r
-                       // fill in the BTC block with raw values\r
-                       //\r
-                       for ( bY = 0; bY < 4; bY++ )\r
-                       {\r
-                               for ( bX = 0; bX < 4; bX++ )\r
-                               {\r
-                                       btcBlock[bY][bX][0] = src[(y*4+bY)*s_resample_width*4 + (x*4+bX)*4 + 0];\r
-                                       btcBlock[bY][bX][1] = src[(y*4+bY)*s_resample_width*4 + (x*4+bX)*4 + 1];\r
-                                       btcBlock[bY][bX][2] = src[(y*4+bY)*s_resample_width*4 + (x*4+bX)*4 + 2];\r
-                               }\r
-                       }\r
-\r
-                       error += BTCCompressBlock( btcBlock, &dst[(y*s_resample_width/4+x)*2] );\r
-               }\r
-       }\r
-\r
-       return error / ( ( s_resample_width / 4 ) * ( s_resample_height / 4 ) );\r
-}\r
-\r
-/*\r
-===================\r
-LoadFrame\r
-===================\r
-*/\r
-cblock_t LoadFrame (char *base, int frame, int digits, byte **palette)\r
-{\r
-       int                     ten3, ten2, ten1, ten0;\r
-       cblock_t        in;\r
-       int                     width, height;\r
-       char            name[1024];\r
-       FILE            *f;\r
-\r
-       in.data = NULL;\r
-       in.count = -1;\r
-\r
-       ten3 = frame/1000;\r
-       ten2 = (frame-ten3*1000)/100;\r
-       ten1 = (frame-ten3*1000-ten2*100)/10;\r
-       ten0 = frame%10;\r
-\r
-       if (digits == 4)\r
-               sprintf (name, "%svideo/%s/%s%i%i%i%i.tga", gamedir, base, base, ten3, ten2, ten1, ten0);\r
-       else\r
-               sprintf (name, "%svideo/%s/%s%i%i%i.tga", gamedir, base, base, ten2, ten1, ten0);\r
-\r
-       f = fopen(name, "rb");\r
-       if (!f)\r
-       {\r
-               in.data = NULL;\r
-               return in;\r
-       }\r
-       fclose (f);\r
-\r
-       printf ("%s", name);\r
-       LoadTGA( name, ( unsigned char ** ) &in.data, &width, &height );\r
-       if ( palette )\r
-               *palette = 0;\r
-//     Load256Image (name, &in.data, palette, &width, &height);\r
-       in.count = width*height;\r
-       in.width = width;\r
-       in.height = height;\r
-// FIXME: map 0 and 255!\r
-\r
-#if 0\r
-       // rle compress\r
-       rle = RLE(in);\r
-       free (in.data);\r
-\r
-       return rle;\r
-#endif\r
-\r
-       return in;\r
-}\r
-\r
-/*\r
-===============\r
-Cmd_Video\r
-\r
-video <directory> <framedigits>\r
-===============\r
-*/\r
-void Cmd_Video (void)\r
-{\r
-       float sumError = 0, error = 0, maxError = 0;\r
-       char    savename[1024];\r
-       char    name[1024];\r
-       FILE    *output;\r
-       int             startframe, frame;\r
-       int             width, height;\r
-       int             i;\r
-       int             digits;\r
-       int             minutes;\r
-       float   fseconds;\r
-       int             remSeconds;\r
-       cblock_t        in;\r
-       unsigned char *resampled;\r
-       unsigned long *compressed;\r
-       clock_t start, stop;\r
-\r
-       GetToken (qfalse);\r
-       strcpy (s_base, token);\r
-       if (g_release)\r
-       {\r
-//             sprintf (savename, "video/%s.cin", token);\r
-//             ReleaseFile (savename);\r
-               return;\r
-       }\r
-\r
-       GetToken( qfalse );\r
-       strcpy( s_output_base, token );\r
-\r
-       GetToken (qfalse);\r
-       digits = atoi(token);\r
-\r
-       GetToken( qfalse );\r
-\r
-       if ( !strcmp( token, "btc" ) )\r
-       {\r
-               s_compression_method = BTC_COMPRESSION;\r
-               printf( "Compression: BTC\n" );\r
-       }\r
-       else if ( !strcmp( token, "uc" ) )\r
-       {\r
-               s_compression_method = UNCOMPRESSED;\r
-               printf( "Compression: none\n" );\r
-       }\r
-       else\r
-       {\r
-               Error( "Uknown compression method '%s'\n", token );\r
-       }\r
-\r
-       GetToken( qfalse );\r
-       s_resample_width = atoi( token );\r
-\r
-       GetToken( qfalse );\r
-       s_resample_height = atoi( token );\r
-\r
-       resampled = malloc( sizeof( unsigned char ) * 4 * s_resample_width * s_resample_height );\r
-       compressed = malloc( sizeof( long ) * 2 * ( s_resample_width / 4 ) * ( s_resample_height / 4 ) );\r
-\r
-       printf( "Resample width: %d\n", s_resample_width );\r
-       printf( "Resample height: %d\n", s_resample_height );\r
-\r
-       // optionally skip frames\r
-       if (TokenAvailable ())\r
-       {\r
-               GetToken (qfalse);\r
-               startframe = atoi(token);\r
-       }\r
-       else\r
-               startframe=0;\r
-\r
-       sprintf (savename, "%svideo/%s.%s", writedir, s_output_base, CIN_EXTENSION );\r
-\r
-       // load the entire sound wav file if present\r
-       LoadSoundtrack ();\r
-\r
-       if (digits == 4)\r
-               sprintf (name, "%svideo/%s/%s0000.tga", gamedir, s_base, s_base);\r
-       else\r
-               sprintf (name, "%svideo/%s/%s000.tga", gamedir, s_base, s_base);\r
-\r
-       printf ("%s\n", name);\r
-       LoadTGA( name, NULL, &width, &height);\r
-\r
-       output = fopen (savename, "wb");\r
-       if (!output)\r
-               Error ("Can't open %s", savename);\r
-\r
-       // write header info\r
-       i = LittleLong( CIN_SIGNATURE );\r
-       fwrite (&i, 4, 1, output );\r
-       i = LittleLong (s_resample_width);\r
-       fwrite (&i, 4, 1, output);\r
-       i = LittleLong (s_resample_height);\r
-       fwrite (&i, 4, 1, output);\r
-       i = LittleLong (s_wavinfo.rate);\r
-       fwrite (&i, 4, 1, output);\r
-       i = LittleLong (s_wavinfo.width);\r
-       fwrite (&i, 4, 1, output);\r
-       i = LittleLong (s_wavinfo.channels);\r
-       fwrite (&i, 4, 1, output);\r
-       i = LittleLong ( s_compression_method );\r
-       fwrite (&i, 4, 1, output );\r
-\r
-       start = clock();\r
-\r
-       // perform compression on a per frame basis\r
-       for ( frame=startframe ;  ; frame++)\r
-       {\r
-               printf ("%02d: ", frame);\r
-               in = LoadFrame (s_base, frame, digits, 0 );\r
-               if (!in.data)\r
-                       break;\r
-\r
-               ResampleFrame( &in, ( unsigned char * ) resampled, BOX_FILTER, s_resample_width, s_resample_height );\r
-\r
-               if ( s_compression_method == UNCOMPRESSED )\r
-               {\r
-                       printf( "\n" );\r
-                       fwrite( resampled, 1, sizeof( unsigned char ) * s_resample_width * s_resample_height * 4, output );\r
-\r
-#if OUTPUT_TGAS\r
-                       {\r
-                               int x, y;\r
-                               char buffer[1000];\r
-\r
-                               for ( y = 0; y < s_resample_height/2; y++ )\r
-                               {\r
-                                       for ( x = 0; x < s_resample_width; x++ )\r
-                                       {\r
-                                               unsigned char tmp[4];\r
-\r
-                                               tmp[0] = resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 0];\r
-                                               tmp[1] = resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 1];\r
-                                               tmp[2] = resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 2];\r
-                                               tmp[3] = resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 3];\r
-\r
-                                               resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 0] = resampled[y*s_resample_width*4 + x*4 + 0];\r
-                                               resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 1] = resampled[y*s_resample_width*4 + x*4 + 1];\r
-                                               resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 2] = resampled[y*s_resample_width*4 + x*4 + 2];\r
-                                               resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 3] = resampled[y*s_resample_width*4 + x*4 + 3];\r
-\r
-                                               resampled[y*s_resample_width*4 + x*4 + 0] = tmp[0];\r
-                                               resampled[y*s_resample_width*4 + x*4 + 1] = tmp[1];\r
-                                               resampled[y*s_resample_width*4 + x*4 + 2] = tmp[2];\r
-                                               resampled[y*s_resample_width*4 + x*4 + 3] = tmp[3];\r
-                                       }\r
-                               }\r
-\r
-                               sprintf( buffer, "%svideo/%s/uc%04d.tga", gamedir, s_base, frame );\r
-                               WriteTGA( buffer, resampled, s_resample_width, s_resample_height );\r
-                       }\r
-#endif\r
-               }\r
-               else if ( s_compression_method == BTC_COMPRESSION )\r
-               {\r
-                       error = BTCCompressFrame( resampled, compressed );\r
-\r
-                       sumError += error;\r
-\r
-                       if ( error > maxError ) \r
-                               maxError = error;\r
-\r
-                       printf( " (error = %f)\n", error );\r
-                       fwrite( compressed, 1, 2 * sizeof( long ) * ( s_resample_width / 4 ) * ( s_resample_height / 4 ), output );\r
-\r
-#if OUTPUT_TGAS\r
-                       {\r
-                               int x, y;\r
-                               unsigned char *uncompressed;\r
-                               char buffer[1000];\r
-\r
-                               uncompressed = malloc( sizeof( unsigned char ) * 4 * s_resample_width * s_resample_height );\r
-                               BTCDecompressFrame( compressed, uncompressed );\r
-\r
-                               for ( y = 0; y < s_resample_height/2; y++ )\r
-                               {\r
-                                       for ( x = 0; x < s_resample_width; x++ )\r
-                                       {\r
-                                               unsigned char tmp[4];\r
-\r
-                                               tmp[0] = uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 0];\r
-                                               tmp[1] = uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 1];\r
-                                               tmp[2] = uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 2];\r
-                                               tmp[3] = uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 3];\r
-\r
-                                               uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 0] = uncompressed[y*s_resample_width*4 + x*4 + 0];\r
-                                               uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 1] = uncompressed[y*s_resample_width*4 + x*4 + 1];\r
-                                               uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 2] = uncompressed[y*s_resample_width*4 + x*4 + 2];\r
-                                               uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 3] = uncompressed[y*s_resample_width*4 + x*4 + 3];\r
-\r
-                                               uncompressed[y*s_resample_width*4 + x*4 + 0] = tmp[0];\r
-                                               uncompressed[y*s_resample_width*4 + x*4 + 1] = tmp[1];\r
-                                               uncompressed[y*s_resample_width*4 + x*4 + 2] = tmp[2];\r
-                                               uncompressed[y*s_resample_width*4 + x*4 + 3] = tmp[3];\r
-                                       }\r
-                               }\r
-\r
-\r
-                               sprintf( buffer, "%svideo/%s/btc%04d.tga", gamedir, s_base, frame );\r
-                               WriteTGA( buffer, uncompressed, s_resample_width, s_resample_height );\r
-\r
-                               free( uncompressed );\r
-                       }\r
-#endif\r
-               }\r
-\r
-               WriteSound( output, frame );\r
-\r
-               free (in.data);\r
-       }\r
-       stop = clock();\r
-\r
-       printf ("\n");\r
-\r
-       printf ("Total size: %i\n", ftell( output ) );\r
-       printf ("Average error: %f\n", sumError / ( frame - startframe ) );\r
-       printf ("Max error: %f\n", maxError );\r
-\r
-       fseconds = ( stop - start ) / 1000.0f;\r
-       minutes = fseconds / 60;\r
-       remSeconds = fseconds - minutes * 60;\r
-\r
-       printf ("Total time: %d s (%d m %d s)\n", ( int ) fseconds, minutes, remSeconds );\r
-       printf ("Time/frame: %.2f seconds\n", fseconds / ( frame - startframe ) );\r
-\r
-       fclose (output);\r
-\r
-       if ( s_soundtrack )\r
-       {\r
-               free( s_soundtrack );\r
-               s_soundtrack = 0;\r
-       }\r
-}\r
+#include <assert.h>
+#include "q3data.h"
+
+static int s_resample_width = 256;
+static int s_resample_height = 256;
+
+#define OUTPUT_TGAS                    1
+
+#define UNCOMPRESSED           0
+#define BTC_COMPRESSION                1
+
+static int s_compression_method = BTC_COMPRESSION;
+
+static const char *CIN_EXTENSION = "cn2";
+static const int CIN_SIGNATURE = ( 'C' << 24 ) | ( 'I' << 16 ) | ( 'N' << 8 ) | ( '2' );
+
+static byte    *s_soundtrack;
+static char    s_base[32];
+static char    s_output_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;
+
+
+static int                     s_samplecounts[0x10000];
+static wavinfo_t       s_wavinfo;
+
+short GetLittleShort(void)
+{
+       short val = 0;
+       val = *data_p;
+       val = val + (*(data_p+1)<<8);
+       data_p += 2;
+       return val;
+}
+
+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;
+}
+
+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(data_p, name, 4))
+                       return;
+       }
+}
+
+void FindChunk(char *name)
+{
+       last_chunk = iff_data;
+       FindNextChunk (name);
+}
+
+
+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);
+}
+
+/*
+============
+GetWavinfo
+============
+*/
+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(data_p+8, "WAVE", 4)))
+       {
+               printf("Missing RIFF/WAVE chunks\n");
+               return info;
+       }
+
+// get "fmt " chunk
+       iff_data = data_p + 12;
+// DumpChunks ();
+
+       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 (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;
+}
+
+//=====================================================================
+
+/*
+==============
+LoadSoundtrack
+==============
+*/
+void LoadSoundtrack (void)
+{
+       char    name[1024];
+       FILE    *f;
+       int             len;
+       int     i, val, j;
+
+       s_soundtrack = NULL;
+       sprintf (name, "%svideo/%s/%s.wav", gamedir, s_base, s_base);
+       printf ("WAV: %s\n", name);
+       f = fopen (name, "rb");
+       if (!f)
+       {
+               printf ("no soundtrack for %s\n", s_base);
+               return;
+       }
+       len = Q_filelength(f);
+       s_soundtrack = malloc(len);
+       fread (s_soundtrack, 1, len, f);
+       fclose (f);
+
+       s_wavinfo = GetWavinfo (name, s_soundtrack, len);
+
+       // count samples for compression
+       memset (s_samplecounts, 0, sizeof(s_samplecounts));
+
+       j = s_wavinfo.samples/2;
+       for (i=0 ; i<j ; i++)
+       {
+               val = ((unsigned short *)( s_soundtrack + s_wavinfo.dataofs))[i];
+               s_samplecounts[val]++;
+       }
+       val = 0;
+       for (i=0 ; i<0x10000 ; i++)
+               if (s_samplecounts[i])
+                       val++;
+
+       printf ("%i unique sample values\n", val);
+}
+
+/*
+==================
+WriteSound
+==================
+*/
+void WriteSound (FILE *output, int frame)
+{
+       int             start, end;
+       int             count;
+       int             empty = 0;
+       int             i;
+       int             sample;
+       int             width;
+
+       width = s_wavinfo.width * s_wavinfo.channels;
+
+       start = frame*s_wavinfo.rate/14;
+       end = (frame+1)*s_wavinfo.rate/14;
+       count = end - start;
+
+       for (i=0 ; i<count ; i++)
+       {
+               sample = start+i;
+               if (sample > s_wavinfo.samples || !s_soundtrack)
+                       fwrite (&empty, 1, width, output);
+               else
+                       fwrite (s_soundtrack + s_wavinfo.dataofs + sample*width, 1, width,output);
+       }
+}
+
+//==========================================================================
+
+static float s_resampleXRatio;
+static float s_resampleYRatio;
+
+static void BoxFilterHorizontalElements( unsigned char *dst, unsigned char *src, float s0, float s1 )
+{
+       float w;
+       float rSum = 0, gSum = 0, bSum = 0;
+       float x = s0;
+       float sumWeight = 0;
+
+       for ( x = s0; x < s1; x++, src += 4 )
+       {
+               if ( x == s0 )
+               {
+                       w = ( int ) ( s0 + 1 ) - x;
+               }
+               else if ( x + 1 >= s1 )
+               {
+                       w = s1 - ( int ) x;
+               }
+               else
+               {
+                       w = 1.0f;
+               }
+
+               rSum += src[0] * w;
+               gSum += src[1] * w;
+               bSum += src[2] * w;
+               sumWeight += w;
+       }
+
+       rSum /= sumWeight;
+       gSum /= sumWeight;
+       bSum /= sumWeight;
+
+       dst[0] = ( unsigned char ) ( rSum + 0.5 );
+       dst[1] = ( unsigned char ) ( gSum + 0.5 );
+       dst[2] = ( unsigned char ) ( bSum + 0.5 );
+}
+
+static void BoxFilterVerticalElements( unsigned char *dst, // destination of the filter process
+                                                                          unsigned char *src, // source pixels
+                                                                          int srcStep,            // stride of the source pixels
+                                                                          float s0, float s1 )
+{
+       float w;
+       float rSum = 0, gSum = 0, bSum = 0;
+       float y = s0;
+       float sumWeight = 0;
+
+       for ( y = s0; y < ( int ) ( s1 + 1 ) ; y++, src += srcStep )
+       {
+               if ( y == s0 )
+               {
+                       w = ( int ) ( s0 + 1 ) - y;
+               }
+               else if ( y + 1 >= s1 )
+               {
+                       w = s1 - ( int ) y;
+               }
+               else
+               {
+                       w = 1.0f;
+               }
+
+               rSum += src[0] * w;
+               gSum += src[1] * w;
+               bSum += src[2] * w;
+               sumWeight += w;
+       }
+
+       rSum /= sumWeight;
+       gSum /= sumWeight;
+       bSum /= sumWeight;
+
+       dst[0] = ( unsigned char ) ( rSum + 0.5 );
+       dst[1] = ( unsigned char ) ( gSum + 0.5 );
+       dst[2] = ( unsigned char ) ( bSum + 0.5 );
+       dst[3] = 0xff;
+
+}
+
+static void BoxFilterRow( unsigned char *dstStart, cblock_t *in, int dstRow, int rowWidth )
+{
+       int i;
+       unsigned char *indata = ( unsigned char * ) in->data;
+
+       indata += 4 * dstRow * in->width;
+
+       for ( i = 0; i < rowWidth; i++ )
+       {
+               float c0 = i * s_resampleXRatio;
+               float c1 = ( i + 1 ) * s_resampleXRatio;
+
+               BoxFilterHorizontalElements( &dstStart[i*4], &indata[( ( int ) c0 ) * 4], c0, c1 );
+       }
+}
+
+static void BoxFilterColumn( unsigned char *dstStart, unsigned char *srcStart, int dstCol, int dstRowWidth, int dstColHeight, int srcRowWidthInPels )
+{
+       float c0, c1;
+       int i;
+
+       for ( i = 0; i < dstColHeight; i++ )
+       {
+               c0 = i * s_resampleYRatio;
+               c1 = ( i + 1 ) * s_resampleYRatio;
+
+               BoxFilterVerticalElements( &dstStart[i*4*dstRowWidth], &srcStart[(int)c0*srcRowWidthInPels*4], srcRowWidthInPels*4, c0, c1 );
+       }
+}
+
+#define DROP_SAMPLE            0
+#define BOX_FILTER             1
+
+static void ResampleFrame( cblock_t *in, unsigned char *out, int method, int outWidth, int outHeight )
+{
+       int row, column;
+       unsigned char *indata = ( unsigned char * ) in->data;
+
+       s_resampleXRatio = in->width / ( float ) outWidth;
+       s_resampleYRatio = in->height / ( float ) outHeight;
+
+       if ( method == DROP_SAMPLE )
+       {
+               for ( row = 0; row < outHeight; row++ )
+               {
+                       int r = ( int ) ( row * s_resampleYRatio );
+
+                       for ( column = 0; column < outWidth; column++ )
+                       {
+                               int c = ( int ) ( column * s_resampleXRatio );
+
+                               out[(row*outWidth+column)*4+0] = indata[(r*in->width+c)*4+0];
+                               out[(row*outWidth+column)*4+1] = indata[(r*in->width+c)*4+1];
+                               out[(row*outWidth+column)*4+2] = indata[(r*in->width+c)*4+2];
+                               out[(row*outWidth+column)*4+3] = 0xff;
+                       }
+               }
+       }
+       else if ( method == BOX_FILTER )
+       {
+               unsigned char intermediate[1024*1024*4];
+
+               assert( in->height <= 1024 );
+               assert( in->width <= 1024 );
+
+               //
+               // filter our M x N source image into a RESAMPLE_WIDTH x N horizontally filtered image
+               //
+               for ( row = 0; row < in->height; row++ )
+               {
+                       BoxFilterRow( &intermediate[row*4*outWidth], in, row, outWidth );
+               }
+
+               //
+               // filter our RESAMPLE_WIDTH x N horizontally filtered image into a RESAMPLE_WIDTH x RESAMPLE_HEIGHT filtered image
+               //
+               for ( column = 0; column < outWidth; column++ )
+               {
+                       BoxFilterColumn( &out[column*4], &intermediate[column*4], column, outWidth, outHeight, s_resample_width );
+               }
+       }
+}
+
+static float BTCDistanceSquared( float a[3], float b[3] )
+{
+       return ( b[0] - a[0] ) * ( b[0] - a[0] ) + 
+                  ( b[1] - a[1] ) * ( b[1] - a[1] ) +
+                  ( b[2] - a[2] ) * ( b[2] - a[2] );
+}
+
+static void BTCFindEndpoints( float inBlock[4][4][3], unsigned int endPoints[2][2] )
+{
+       float longestDistance = -1;
+
+       int bX, bY;
+
+       //
+       // find the two points farthest from each other
+       //
+       for ( bY = 0; bY < 4; bY++ )
+       {
+               for ( bX = 0; bX < 4; bX++ )
+               {
+                       int cX, cY;
+                       float d;
+
+                       //
+                       // check the rest of the current row
+                       //
+                       for ( cX = bX + 1; cX < 4; cX++ )
+                       {
+                               if ( ( d = BTCDistanceSquared( inBlock[bY][bX], inBlock[bY][cX] ) ) > longestDistance )
+                               {
+                                       longestDistance = d;
+                                       endPoints[0][0] = bX;
+                                       endPoints[0][1] = bY;
+                                       endPoints[1][0] = cX;
+                                       endPoints[1][1] = bY;
+                               }
+                       }
+
+                       //
+                       // check remaining rows and columns
+                       //
+                       for ( cY = bY+1; cY < 4; cY++ )
+                       {
+                               for ( cX = 0; cX < 4; cX++ )
+                               {
+                                       if ( ( d = BTCDistanceSquared( inBlock[bY][bX], inBlock[cY][cX] ) ) > longestDistance )
+                                       {
+                                               longestDistance = d;
+                                               endPoints[0][0] = bX;
+                                               endPoints[0][1] = bY;
+                                               endPoints[1][0] = cX;
+                                               endPoints[1][1] = cY;
+                                       }
+                               }
+                       }
+               }
+       }
+}
+
+static float BTCQuantizeBlock( float inBlock[4][4][3], unsigned long endPoints[2][2], int btcQuantizedBlock[4][4], float bestError )
+{
+       int i;
+       int blockY, blockX;
+       float dR, dG, dB;
+       float R, G, B;
+       float error = 0;
+       float colorLine[4][3];
+
+       //
+       // build the color line
+       //
+       dR = inBlock[endPoints[1][1]][endPoints[1][0]][0] -
+                inBlock[endPoints[0][1]][endPoints[0][0]][0];
+       dG = inBlock[endPoints[1][1]][endPoints[1][0]][1] -
+                inBlock[endPoints[0][1]][endPoints[0][0]][1];
+       dB = inBlock[endPoints[1][1]][endPoints[1][0]][2] -
+                inBlock[endPoints[0][1]][endPoints[0][0]][2];
+
+       dR *= 0.33f;
+       dG *= 0.33f;
+       dB *= 0.33f;
+
+       R = inBlock[endPoints[0][1]][endPoints[0][0]][0];
+       G = inBlock[endPoints[0][1]][endPoints[0][0]][1];
+       B = inBlock[endPoints[0][1]][endPoints[0][0]][2];
+
+       for ( i = 0; i < 4; i++ )
+       {
+               colorLine[i][0] = R;
+               colorLine[i][1] = G;
+               colorLine[i][2] = B;
+
+               R += dR;
+               G += dG;
+               B += dB;
+       }
+
+       //
+       // quantize each pixel into the appropriate range
+       //
+       for ( blockY = 0; blockY < 4; blockY++ )
+       {
+               for ( blockX = 0; blockX < 4; blockX++ )
+               {
+                       float distance = 10000000000;
+                       int shortest = -1;
+
+                       for ( i = 0; i < 4; i++ )
+                       {
+                               float d;
+
+                               if ( ( d = BTCDistanceSquared( inBlock[blockY][blockX], colorLine[i] ) ) < distance )
+                               {
+                                       distance = d;
+                                       shortest = i;
+                               }
+                       }
+
+                       error += distance;
+
+                       //
+                       // if bestError is not -1 then that means this is a speculative quantization
+                       //
+                       if ( bestError != -1 )
+                       {
+                               if ( error > bestError )
+                                       return error;
+                       }
+
+                       btcQuantizedBlock[blockY][blockX] = shortest;
+               }
+       }
+
+       return error;
+}
+
+/*
+** float BTCCompressBlock
+*/
+static float BTCCompressBlock( float inBlock[4][4][3], unsigned long out[2] )
+{
+       int i;
+       int btcQuantizedBlock[4][4];    // values should be [0..3]
+       unsigned long encodedEndPoints, encodedBitmap;
+       unsigned int endPoints[2][2];           // endPoints[0] = color start, endPoints[1] = color end
+       int blockY, blockX;
+       float error = 0;
+       float bestError = 10000000000;
+       unsigned int bestEndPoints[2][2];
+
+#if 0
+       //
+       // find the "ideal" end points for the color vector 
+       //
+       BTCFindEndpoints( inBlock, endPoints );
+       error = BTCQuantizeBlock( inBlock, endPoints, btcQuantizedBlock );
+       memcpy( bestEndPoints, endPoints, sizeof( bestEndPoints ) );
+#else
+       for ( blockY = 0; blockY < 4; blockY++ )
+       {
+               for ( blockX = 0; blockX < 4; blockX++ )
+               {
+                       int x2, y2;
+
+                       for ( y2 = 0; y2 < 4; y2++ )
+                       {
+                               for ( x2 = 0; x2 < 4; x2++ )
+                               {
+                                       if ( ( x2 == blockX ) && ( y2 == blockY ) )
+                                               continue;
+
+                                       endPoints[0][0] = blockX;
+                                       endPoints[0][1] = blockY;
+                                       endPoints[1][0] = x2;
+                                       endPoints[1][1] = y2;
+
+                                       error = BTCQuantizeBlock( inBlock, endPoints, btcQuantizedBlock, -1 ); //bestError );
+
+                                       if ( error < bestError )
+                                       {
+                                               bestError = error;
+                                               memcpy( bestEndPoints, endPoints, sizeof( bestEndPoints ) );
+                                       }
+                               }
+                       }
+               }
+       }
+
+       error = BTCQuantizeBlock( inBlock, bestEndPoints, btcQuantizedBlock, -1.0f );
+#endif
+
+       //
+       // encode the results
+       //
+       encodedBitmap = 0;
+       for ( blockY = 0; blockY < 4; blockY++ )
+       {
+               for ( blockX = 0; blockX < 4; blockX++ )
+               {
+                       int shift = ( blockX + blockY * 4 ) * 2;
+                       encodedBitmap |= btcQuantizedBlock[blockY][blockX] << shift;
+               }
+       }
+
+       //
+       // encode endpoints
+       //
+       encodedEndPoints = 0;
+       for ( i = 0; i < 2; i++ )
+       {
+               int iR, iG, iB;
+
+               iR = ( ( int ) inBlock[bestEndPoints[i][1]][bestEndPoints[i][0]][0] );
+               if ( iR > 255 ) 
+                       iR = 255;
+               else if ( iR < 0 ) 
+                       iR = 0;
+               iR >>= 3;
+
+               iG = ( ( int ) inBlock[bestEndPoints[i][1]][bestEndPoints[i][0]][1] );
+               if ( iG > 255 )
+                       iG = 255;
+               else if ( iG < 0 )
+                       iG = 0;
+               iG >>= 2;
+
+               iB = ( ( int ) inBlock[bestEndPoints[i][1]][bestEndPoints[i][0]][2] );
+               if ( iB > 255 )
+                       iB = 255;
+               else if ( iB < 0 )
+                       iB = 0;
+               iB >>= 3;
+
+
+               encodedEndPoints |= ( ( ( iR << 11 ) | ( iG << 5 ) | ( iB ) ) << ( i * 16 ) );
+       }
+
+       //
+       // store 
+       //
+       out[0] = encodedBitmap;
+       out[1] = encodedEndPoints;
+
+       return error;
+}
+
+/*
+** void BTCDecompressFrame
+*/
+static void BTCDecompressFrame( unsigned long *src, unsigned char *dst )
+{
+       int x, y;
+       int iR, iG, iB;
+       int dstX, dstY;
+       float colorStart[3], colorEnd[3];
+       unsigned char colorRampABGR[4][4];
+       unsigned encoded;
+
+       memset( colorRampABGR, 0xff, sizeof( colorRampABGR ) );
+
+       for ( y = 0; y < s_resample_height / 4; y++ )
+       {
+               for ( x = 0; x < s_resample_width / 4; x++ )
+               {
+                       unsigned colorStartPacked = src[(y*s_resample_width/4 + x)*2 + 1] & 0xffff;
+                       unsigned colorEndPacked = src[(y*s_resample_width/4 + x)*2 + 1] >> 16;
+
+                       //
+                       // grab the end points
+                       //   0 = color start
+                       //   1 = color end
+                       //
+                       iR = ( ( colorStartPacked >> 11 ) & ( ( 1 << 5 ) - 1 ) );
+                       iR = ( iR << 3 ) | ( iR >> 2 );
+                       iG = ( ( colorStartPacked >> 5 ) & ( ( 1 << 6 )  - 1 ) );
+                       iG = ( iG << 2 ) | ( iG >> 4 );
+                       iB = ( ( colorStartPacked ) & ( ( 1 << 5  ) - 1 ) );
+                       iB = ( iB << 3 ) | ( iB >> 2 );
+
+                       colorStart[0] = iR;
+                       colorStart[1] = iG;
+                       colorStart[2] = iB;
+                       colorRampABGR[0][0] = iR;
+                       colorRampABGR[0][1] = iG;
+                       colorRampABGR[0][2] = iB;
+
+                       iR = ( ( colorEndPacked >> 11 ) & ( ( 1 << 5 ) - 1 ) );
+                       iR = ( iR << 3 ) | ( iR >> 2 );
+                       iG = ( ( colorEndPacked >> 5 ) & ( ( 1 << 6 )  - 1 ) );
+                       iG = ( iG << 2 ) | ( iG >> 4 );
+                       iB = ( colorEndPacked & ( ( 1 << 5  ) - 1 ) );
+                       iB = ( iB << 3 ) | ( iB >> 2 );
+
+                       colorEnd[0] = iR;
+                       colorEnd[1] = iG;
+                       colorEnd[2] = iB;
+                       colorRampABGR[3][0] = iR;
+                       colorRampABGR[3][1] = iG;
+                       colorRampABGR[3][2] = iB;
+                       
+                       //
+                       // compute this block's color ramp
+                       // FIXME: This needs to be reversed on big-endian machines
+                       //
+                       
+                       colorRampABGR[1][0] = colorStart[0] * 0.66f + colorEnd[0] * 0.33f;
+                       colorRampABGR[1][1] = colorStart[1] * 0.66f + colorEnd[1] * 0.33f;
+                       colorRampABGR[1][2] = colorStart[2] * 0.66f + colorEnd[2] * 0.33f;
+
+                       colorRampABGR[2][0] = colorStart[0] * 0.33f + colorEnd[0] * 0.66f;
+                       colorRampABGR[2][1] = colorStart[1] * 0.33f + colorEnd[1] * 0.66f;
+                       colorRampABGR[2][2] = colorStart[2] * 0.33f + colorEnd[2] * 0.66f;
+
+                       //
+                       // decode the color data
+                       // information is encoded in 2-bit pixels, with low order bits corresponding
+                       // to upper left pixels.  These 2-bit values are indexed into the block's
+                       // computer color ramp.
+                       //
+                       encoded = src[(y*s_resample_width/4 + x)*2 + 0];
+
+                       for ( dstY = 0; dstY < 4; dstY++ )
+                       {
+                               for ( dstX = 0; dstX < 4; dstX++ )
+                               {
+                                       memcpy( &dst[(y*4+dstY)*s_resample_width*4+x*4*4+dstX*4], colorRampABGR[encoded&3], sizeof( colorRampABGR[0] ) );
+                                       encoded >>= 2;
+                               }
+                       }
+               }
+       }
+}
+
+/*
+** BTCCompressFrame
+**
+** Perform a BTC compression using a 2-bit encoding at each pixel.  This
+** compression method is performed by decomposing the incoming image into
+** a sequence of 4x4 blocks.  At each block two color values are computed
+** that define the endpoints of a vector in color space that represent
+** the two colors "farthest apart".
+*/
+static float BTCCompressFrame( unsigned char *src, unsigned long *dst )
+{
+       int x, y;
+       int bX, bY;
+       float btcBlock[4][4][3];
+
+       float error = 0;
+       
+       for ( y = 0; y < s_resample_height / 4; y++ )
+       {
+               for ( x = 0; x < s_resample_width / 4; x++ )
+               {
+                       //
+                       // fill in the BTC block with raw values
+                       //
+                       for ( bY = 0; bY < 4; bY++ )
+                       {
+                               for ( bX = 0; bX < 4; bX++ )
+                               {
+                                       btcBlock[bY][bX][0] = src[(y*4+bY)*s_resample_width*4 + (x*4+bX)*4 + 0];
+                                       btcBlock[bY][bX][1] = src[(y*4+bY)*s_resample_width*4 + (x*4+bX)*4 + 1];
+                                       btcBlock[bY][bX][2] = src[(y*4+bY)*s_resample_width*4 + (x*4+bX)*4 + 2];
+                               }
+                       }
+
+                       error += BTCCompressBlock( btcBlock, &dst[(y*s_resample_width/4+x)*2] );
+               }
+       }
+
+       return error / ( ( s_resample_width / 4 ) * ( s_resample_height / 4 ) );
+}
+
+/*
+===================
+LoadFrame
+===================
+*/
+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.tga", gamedir, base, base, ten3, ten2, ten1, ten0);
+       else
+               sprintf (name, "%svideo/%s/%s%i%i%i.tga", gamedir, base, base, ten2, ten1, ten0);
+
+       f = fopen(name, "rb");
+       if (!f)
+       {
+               in.data = NULL;
+               return in;
+       }
+       fclose (f);
+
+       printf ("%s", name);
+       LoadTGA( name, ( unsigned char ** ) &in.data, &width, &height );
+       if ( palette )
+               *palette = 0;
+//     Load256Image (name, &in.data, palette, &width, &height);
+       in.count = width*height;
+       in.width = width;
+       in.height = height;
+// FIXME: map 0 and 255!
+
+#if 0
+       // rle compress
+       rle = RLE(in);
+       free (in.data);
+
+       return rle;
+#endif
+
+       return in;
+}
+
+/*
+===============
+Cmd_Video
+
+video <directory> <framedigits>
+===============
+*/
+void Cmd_Video (void)
+{
+       float sumError = 0, error = 0, maxError = 0;
+       char    savename[1024];
+       char    name[1024];
+       FILE    *output;
+       int             startframe, frame;
+       int             width, height;
+       int             i;
+       int             digits;
+       int             minutes;
+       float   fseconds;
+       int             remSeconds;
+       cblock_t        in;
+       unsigned char *resampled;
+       unsigned long *compressed;
+       clock_t start, stop;
+
+       GetToken (qfalse);
+       strcpy (s_base, token);
+       if (g_release)
+       {
+//             sprintf (savename, "video/%s.cin", token);
+//             ReleaseFile (savename);
+               return;
+       }
+
+       GetToken( qfalse );
+       strcpy( s_output_base, token );
+
+       GetToken (qfalse);
+       digits = atoi(token);
+
+       GetToken( qfalse );
+
+       if ( !strcmp( token, "btc" ) )
+       {
+               s_compression_method = BTC_COMPRESSION;
+               printf( "Compression: BTC\n" );
+       }
+       else if ( !strcmp( token, "uc" ) )
+       {
+               s_compression_method = UNCOMPRESSED;
+               printf( "Compression: none\n" );
+       }
+       else
+       {
+               Error( "Uknown compression method '%s'\n", token );
+       }
+
+       GetToken( qfalse );
+       s_resample_width = atoi( token );
+
+       GetToken( qfalse );
+       s_resample_height = atoi( token );
+
+       resampled = malloc( sizeof( unsigned char ) * 4 * s_resample_width * s_resample_height );
+       compressed = malloc( sizeof( long ) * 2 * ( s_resample_width / 4 ) * ( s_resample_height / 4 ) );
+
+       printf( "Resample width: %d\n", s_resample_width );
+       printf( "Resample height: %d\n", s_resample_height );
+
+       // optionally skip frames
+       if (TokenAvailable ())
+       {
+               GetToken (qfalse);
+               startframe = atoi(token);
+       }
+       else
+               startframe=0;
+
+       sprintf (savename, "%svideo/%s.%s", writedir, s_output_base, CIN_EXTENSION );
+
+       // load the entire sound wav file if present
+       LoadSoundtrack ();
+
+       if (digits == 4)
+               sprintf (name, "%svideo/%s/%s0000.tga", gamedir, s_base, s_base);
+       else
+               sprintf (name, "%svideo/%s/%s000.tga", gamedir, s_base, s_base);
+
+       printf ("%s\n", name);
+       LoadTGA( name, NULL, &width, &height);
+
+       output = fopen (savename, "wb");
+       if (!output)
+               Error ("Can't open %s", savename);
+
+       // write header info
+       i = LittleLong( CIN_SIGNATURE );
+       fwrite (&i, 4, 1, output );
+       i = LittleLong (s_resample_width);
+       fwrite (&i, 4, 1, output);
+       i = LittleLong (s_resample_height);
+       fwrite (&i, 4, 1, output);
+       i = LittleLong (s_wavinfo.rate);
+       fwrite (&i, 4, 1, output);
+       i = LittleLong (s_wavinfo.width);
+       fwrite (&i, 4, 1, output);
+       i = LittleLong (s_wavinfo.channels);
+       fwrite (&i, 4, 1, output);
+       i = LittleLong ( s_compression_method );
+       fwrite (&i, 4, 1, output );
+
+       start = clock();
+
+       // perform compression on a per frame basis
+       for ( frame=startframe ;  ; frame++)
+       {
+               printf ("%02d: ", frame);
+               in = LoadFrame (s_base, frame, digits, 0 );
+               if (!in.data)
+                       break;
+
+               ResampleFrame( &in, ( unsigned char * ) resampled, BOX_FILTER, s_resample_width, s_resample_height );
+
+               if ( s_compression_method == UNCOMPRESSED )
+               {
+                       printf( "\n" );
+                       fwrite( resampled, 1, sizeof( unsigned char ) * s_resample_width * s_resample_height * 4, output );
+
+#if OUTPUT_TGAS
+                       {
+                               int x, y;
+                               char buffer[1000];
+
+                               for ( y = 0; y < s_resample_height/2; y++ )
+                               {
+                                       for ( x = 0; x < s_resample_width; x++ )
+                                       {
+                                               unsigned char tmp[4];
+
+                                               tmp[0] = resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 0];
+                                               tmp[1] = resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 1];
+                                               tmp[2] = resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 2];
+                                               tmp[3] = resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 3];
+
+                                               resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 0] = resampled[y*s_resample_width*4 + x*4 + 0];
+                                               resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 1] = resampled[y*s_resample_width*4 + x*4 + 1];
+                                               resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 2] = resampled[y*s_resample_width*4 + x*4 + 2];
+                                               resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 3] = resampled[y*s_resample_width*4 + x*4 + 3];
+
+                                               resampled[y*s_resample_width*4 + x*4 + 0] = tmp[0];
+                                               resampled[y*s_resample_width*4 + x*4 + 1] = tmp[1];
+                                               resampled[y*s_resample_width*4 + x*4 + 2] = tmp[2];
+                                               resampled[y*s_resample_width*4 + x*4 + 3] = tmp[3];
+                                       }
+                               }
+
+                               sprintf( buffer, "%svideo/%s/uc%04d.tga", gamedir, s_base, frame );
+                               WriteTGA( buffer, resampled, s_resample_width, s_resample_height );
+                       }
+#endif
+               }
+               else if ( s_compression_method == BTC_COMPRESSION )
+               {
+                       error = BTCCompressFrame( resampled, compressed );
+
+                       sumError += error;
+
+                       if ( error > maxError ) 
+                               maxError = error;
+
+                       printf( " (error = %f)\n", error );
+                       fwrite( compressed, 1, 2 * sizeof( long ) * ( s_resample_width / 4 ) * ( s_resample_height / 4 ), output );
+
+#if OUTPUT_TGAS
+                       {
+                               int x, y;
+                               unsigned char *uncompressed;
+                               char buffer[1000];
+
+                               uncompressed = malloc( sizeof( unsigned char ) * 4 * s_resample_width * s_resample_height );
+                               BTCDecompressFrame( compressed, uncompressed );
+
+                               for ( y = 0; y < s_resample_height/2; y++ )
+                               {
+                                       for ( x = 0; x < s_resample_width; x++ )
+                                       {
+                                               unsigned char tmp[4];
+
+                                               tmp[0] = uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 0];
+                                               tmp[1] = uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 1];
+                                               tmp[2] = uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 2];
+                                               tmp[3] = uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 3];
+
+                                               uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 0] = uncompressed[y*s_resample_width*4 + x*4 + 0];
+                                               uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 1] = uncompressed[y*s_resample_width*4 + x*4 + 1];
+                                               uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 2] = uncompressed[y*s_resample_width*4 + x*4 + 2];
+                                               uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 3] = uncompressed[y*s_resample_width*4 + x*4 + 3];
+
+                                               uncompressed[y*s_resample_width*4 + x*4 + 0] = tmp[0];
+                                               uncompressed[y*s_resample_width*4 + x*4 + 1] = tmp[1];
+                                               uncompressed[y*s_resample_width*4 + x*4 + 2] = tmp[2];
+                                               uncompressed[y*s_resample_width*4 + x*4 + 3] = tmp[3];
+                                       }
+                               }
+
+
+                               sprintf( buffer, "%svideo/%s/btc%04d.tga", gamedir, s_base, frame );
+                               WriteTGA( buffer, uncompressed, s_resample_width, s_resample_height );
+
+                               free( uncompressed );
+                       }
+#endif
+               }
+
+               WriteSound( output, frame );
+
+               free (in.data);
+       }
+       stop = clock();
+
+       printf ("\n");
+
+       printf ("Total size: %i\n", ftell( output ) );
+       printf ("Average error: %f\n", sumError / ( frame - startframe ) );
+       printf ("Max error: %f\n", maxError );
+
+       fseconds = ( stop - start ) / 1000.0f;
+       minutes = fseconds / 60;
+       remSeconds = fseconds - minutes * 60;
+
+       printf ("Total time: %d s (%d m %d s)\n", ( int ) fseconds, minutes, remSeconds );
+       printf ("Time/frame: %.2f seconds\n", fseconds / ( frame - startframe ) );
+
+       fclose (output);
+
+       if ( s_soundtrack )
+       {
+               free( s_soundtrack );
+               s_soundtrack = 0;
+       }
+}