Linux Game Programming for PC & Embedded Systems using SDL
Presented by
Fore June
Author of Windows Fan, Linux Fan

A simple video codec.

codecio.h
runhuf.h
encode.h
decode.h
dct_video.h
common.h
fbitios.h
avilib.h
vcodec.cpp
runhuf.cpp
encode.cpp
decode.cpp
dct_video.cpp
fbitios.cpp
Makefile
sample_video.avi ( not zipped )
avilib.o ( not zipped )



vcodec.cpp:
/*
  vcodec.cpp
  A simple video coder-decoder ( codec ) for intra frames of a video.
  Input file should be an uncompressed avi file with data saved using 
  the 24-bit RGB color model.
  Functions prefixed with "AVI_" are from "avilib.o" provided by mpeg4ip 
  ( http://mpeg4ip.sourceforge.net/ ).
  
  See "http://www.webkinesia.com/games/vcompress8.php" for details.

*/
#include <SDL/SDL.h>
#include <string.h>
#include "common.h"
#include "runhuf.h"
#include "avilib.h"
#include "codecio.h"
#include "encode.h"
#include "decode.h"

#define  ENCODE		0
#define	 DECODE		1
#define  DECODE_SAVE	2
#define	 DECODE_NONE	3

//global variables
unsigned long head = 0,  tail = 0;
unsigned long videoFramesRead = 0;
short decode_flag = ENCODE;
unsigned long frameSize;
FILE *fp_save = NULL;
bitFileIO *outputs;
bitFileIO *inputs;
bool quit = false;
char *buf[4];


//turn the image upside down to make it consistent with MS AVI decode
void up_down_flip ( char *image, int nbytespp, int width, int height )
{
  char temp[nbytespp*width];
  int i, j, h, w, i1, i2;
  h = height/2;
  w = nbytespp * width;
  for ( i = 0; i < h; ++i ) {
    i1 = w * i;
    i2 = w * ( height - i - 1 );
    for ( j = 0; j < w; ++j )
      temp[j] = *( image + i1 + j );

    for ( j = 0; j < w; ++j )
      *(image+i1+j) = *(image + i2 + j );
    for ( j = 0; j < w; ++j )
      *(image + i2 + j ) = temp[j];
  }
}

//Consumer
int player ( void *scr )
{
  SDL_Surface *screen = ( SDL_Surface * ) scr;
  Uint32 prev_time, current_time;
  short  frame_time;

  current_time = SDL_GetTicks();//ms since library starts
  prev_time = SDL_GetTicks();   //ms since library starts
  frame_time = 50;              //frame time in ms, assume fps = 20

  while ( !quit || head < tail ) {
    if ( head >= tail ) {       //buffer empty (data not available yet )
      SDL_Delay ( 10 );         //sleep for 30 ms
      continue;
    }
    //consumes the data
    screen->pixels = buf[head%4];
    if ( current_time - prev_time < frame_time )
      SDL_Delay ( frame_time - ( current_time - prev_time ) );
    prev_time = current_time;

    SDL_UpdateRect ( screen, 0, 0, 0, 0 );      //update whole screen
    head++;
  } //while
  return 0;
}
  
//Producer
int encoder ( void *data )
{
  int n;
  char *image;
  set <RunHuff> htable;
  run3D runs[64];
  avi_t *avi = ( avi_t * ) data;
  long numBytes = 0, totalBytes = 0;
  unsigned long emptyFramesRead = 0;
  unsigned long short_frames_len = 4;
  unsigned  numVideoFrames; 
  numVideoFrames = AVI_video_frames( avi );
  //extract video 
  //move file pointer pointing to beginning of video data
  if (AVI_seek_start( avi )) {
     fprintf(stderr, "bad seek 0: %s\n", AVI_strerror());
     quit = true;
     return 1;
  }
  if (AVI_set_video_position( avi, 0, NULL)) {
     fprintf(stderr, "bad seek 1: %s\n", AVI_strerror());
     quit = true;
     return 1;
  }
  build_htable ( htable );
  //print_htable ( htable );
  videoFramesRead = 0;
  while ( !quit ) {
    if ( tail >= head + 4 ) {  		//buffer full
        SDL_Delay ( 10 );       	//sleep for 10 ms ( better to synchronize using 
					//  a semaphore; let consumer wake you up )
        continue;
    }
    //produce data
    image = buf[tail%4];
    numBytes = AVI_read_frame( avi, (char *)image );
    totalBytes += numBytes;
    videoFramesRead++;
    printf("frame %d - length %u total %u\r", videoFramesRead, numBytes, totalBytes);
     //eliminate short frames
    if ( numBytes <= short_frames_len )
      emptyFramesRead++;
     // read error
    if ( videoFramesRead >= numVideoFrames ) quit = true;
    if (emptyFramesRead) {
    	fprintf(stderr, "warning: %u zero length frames ignored\n", emptyFramesRead);
    }
    if (numBytes < 0) { 
    	fprintf(stderr, "read error\n" );
	quit = true;
    } else {
        up_down_flip ( image, 3, avi->width, avi->height );	//flip the image for display
     	encode_one_frame ( image, avi->width, avi->height, outputs, htable );
        tail++;
    }
  } //while
  return 0;
}

//Producer
int decoder ( void *data )
{
  char *image;
  int i, numBytes = 0;

  VHEADER *hp = ( VHEADER *) data;
  int frameSize = hp->width * hp->height * 3;
  int framesDecoded = 0;
  
  while ( !quit ) {
    if ( tail >= head + 4 ) {  		//buffer full
        SDL_Delay ( 10 );       	//sleep for 10 ms ( better to synchronize using 
					//  a semaphore; let consumer wake you up )
        continue;
    }
    //produce data
    image = buf[tail%4];		//points to empty buffer
    if ( decode_flag == DECODE_NONE ){	//no need to decode
      for ( i = 0; i < frameSize; ++i ) 
	image[i] = inputs->inputBits ( 8 );
    } else
      numBytes = decode_ybrFrame ( inputs, image, hp->width, hp->height );
    if ( decode_flag == DECODE_SAVE ) {	//save decoded frame
      if ( numBytes <= 0 )
	quit = true;
      else if (  fwrite ( image, 1, numBytes, fp_save ) != numBytes ) {
        fprintf ( stderr, "\nError writing file!\n" );
        quit = true;
      }
    }
    tail++;
    framesDecoded++;
    if ( framesDecoded >= hp->nframes ) 
      quit = true;
  } //while
  if ( decode_flag == DECODE_SAVE )
    fclose ( fp_save ); 
  return 0;
}

/*
  Functions prefixed with "AVI_" are functions from "avilib.o". 
*/
int main( int argc, char *argv[] )
{
  char infilename[30], outfilename[30];	//input and output file names

  //variables for avi file
  unsigned long fileDuration;
  unsigned  numVideoFrames; 
  double videoFrameRate; 
  avi_t *avi = NULL;			//points at opened avi file
  VHEADER  vheader;			//.fjv ( compressed ) file header
  char *hp;				//points to .fjv file header
  int i;
 
  if ( argc < 2 ) {
    printf("\nUsage: vcodec [-d|-s] infile [outfile]");
    printf("\n  Default is encoding, encoded data saved.");
    printf("\n  -d : Decoding, decoded data not saved");
    printf("\n  -s : Decoding, decoded data saved" );
    printf("\nExamples: ");
    printf("\n	vcodec sample.avi	;output in sample.fjv");
    printf("\n	vcodec -d sample.fjv	;output not saved");
    printf("\n	vcodec -s sample.fjv	;output in sample_d.fjv\n");
    exit ( 0 );
  } 
  argv++;
  if ( (*argv)[0] == '-' ) {			//decoding
    if ( argc < 3 ) {
      printf("\nUsage: vcodec [-d|-s] infile [outfile]\n");
      exit ( 1 );
    }
    if ( (*argv)[1] == 'd' )			//decode and play without saving decoded data
      decode_flag = DECODE;			//default is ENCODE
    else if ( (*argv)[1] == 's' )		//decode, play and save decoded data
      decode_flag = DECODE_SAVE;
    argv++;
  } else
    argc++;					//take care of default case
  strcpy ( infilename, *argv++ );

  if ( argc < 4 ) {				//construct compressed video output file name
    strcpy ( outfilename, infilename );
    char *p_dot = strrchr ( outfilename, '.' );	//p_dot points to last occurrence of '.'
    if ( p_dot != NULL ) *p_dot = 0;		//replace '.' by 0
    if ( decode_flag == ENCODE )		//encoding
      strcat ( outfilename, ".fjv" );
    else if ( decode_flag == DECODE_SAVE )	//decoding and saving decoded data
      strcat ( outfilename, "_d.fjv" );  	//file containing decoded data
  } else
    strcpy ( outfilename, *argv++ ); 		//use output filename provided
  
  if ( decode_flag == ENCODE ) {		//encoding
    avi = AVI_open_input_file( infilename, 1 );
    if (avi == NULL) {
      fprintf(stderr, "error %s: %s\n", infilename, AVI_strerror());
      exit( 2 );
    }
    videoFrameRate = AVI_video_frame_rate(avi);	//frame per second
    numVideoFrames = AVI_video_frames( avi );	//number of video frames in file
    fileDuration = ( unsigned long ) ceil( numVideoFrames / videoFrameRate);

    printf("\n---------------------------------------------------------");
    printf("\nVideo Frame Rate:\t%5.0f \tfps", videoFrameRate );
    printf("\nNumber of Frames:\t%5d", numVideoFrames );
    printf("\nFile Duration:   \t%5ld \tsec", fileDuration );
    printf("\nVideo width:     \t%5d \tpixels", avi->width );
    printf("\nVideo height:     \t%5d \tpixels", avi->height );
    printf("\nCompressor type: \t%5s", avi->compressor );
    printf("\nAudio channels:  \t%5d", avi->a_chans );
    printf("\nAudio sample rate:\t%5d \tHz", avi->a_rate  );
    printf("\nBits per audio sample:\t%5d", avi->a_bits );

    printf("\nVideo starts at:  \t%5ld ( 0x%lx )", avi->movi_start, avi->movi_start );
    printf("\n---------------------------------------------------------\n");
    
    bzero((void *) &vheader,sizeof (vheader ));	//clear header
    strcpy ( vheader.id, "FORJUNEV" );		//set header I.D.
    vheader.fps = ( short )videoFrameRate;	//frame per second
    vheader.nframes = numVideoFrames;		//number of frames
    vheader.width = avi->width;			//image width
    vheader.height = avi->height;		//image height
    vheader.bpp = 8;				//number of bits per pixel
    vheader.qmethod = 1;			//quantization method
    vheader.ext = 1;				//file contains compressed data
    outputs = new bitFileIO ( outfilename, 0 );	//open file for output
						//save header in file
    for ( i = 0, hp = ( char *)&vheader; i < sizeof ( vheader ); ++i ) 
      outputs->outputBits( hp[i], 8 );
  }  else  {					//DECODE or DECODE_SAVE
    inputs = new bitFileIO ( infilename, 1 );	//open file for input
						//read header from file
    for ( i = 0, hp = ( char *)&vheader; i < sizeof ( vheader ); ++i ) 
      hp[i] = inputs->inputBits( 8 );
    if ( strcmp ( vheader.id, "FORJUNEV" ) != 0 ) {
      printf("File Error: wrong file type!\n");
      return 1;
    } 
    if ( vheader.ext == 0 ) { 			//data not compressed
      decode_flag = DECODE_NONE;		//no need to save data
      printf("\nData not compressed! Output will not be saved!\n");
    }	
    if ( decode_flag == DECODE_SAVE ) {		//need to save output data 	 		
      vheader.ext = 0;				//output data not compressed
      if ( ( fp_save = fopen ( outfilename, "wb" ) ) == NULL ) {
	printf("\nError opening file %s for write.\n", outfilename );
	return 1;
      }
      fwrite ( ( void * ) &vheader, sizeof ( vheader ), 1, fp_save ); 
    }
  }   

  /*
    Create graphics display and producer-consumer threads using SDL.
  */
  frameSize = vheader.width*vheader.height*3;	//assume 24-bit RGB color model
  SDL_Thread *producer, *consumer;
  SDL_Surface *screen;
  SDL_Event event;
  int status;
  char *key;

  //initialize video system
  if ( SDL_Init( SDL_INIT_VIDEO ) < 0 ) {
        fprintf(stderr, "Unable to init SDL: %s\n", SDL_GetError());
        exit(1);
  }
  //ensure SDL_Quit is called when the program exits
  //  atexit(SDL_Quit);

  //set video mode of to width x height with 24-bit pixels
  screen = SDL_SetVideoMode(vheader.width, vheader.height, 24, SDL_SWSURFACE);
  if ( screen == NULL ) {
        fprintf(stderr, "Unable to set %dx%d video: %s\n", 
			vheader.width, vheader.height, SDL_GetError());
        exit(1);
  }

  for ( i = 0; i < 4; ++i ) {			//allocate 4 buffers for queuing frames
    buf[i] = ( char * ) malloc ( frameSize );
    assert ( buf[i] );
  }

  head = tail = 0;
  consumer = SDL_CreateThread (player, screen);	//player is consumer
  if ( decode_flag != ENCODE ) 
    producer = SDL_CreateThread ( decoder, ( void * ) &vheader );//decoder is producer
  else
    producer = SDL_CreateThread ( encoder, ( void * ) avi );	//encoder is producer

  while (!quit)
  {
    status = SDL_WaitEvent(&event);     //wait indefinitely for an event to occur
                                        //event will be removed from event queue
    if ( !status ) {                    //Error has occured while waiting
        printf("SDL_WaitEvent error: %s\n", SDL_GetError());
        quit = true;
        return false;
    }
    switch (event.type) {               //check the event type
      case SDL_KEYDOWN:                 //if a key has been pressed
        key = SDL_GetKeyName(event.key.keysym.sym);
        printf("The %s key was pressed!\n", key );
        if ( event.key.keysym.sym == SDLK_ESCAPE )      //quit if 'ESC' pressed
          quit = true;
        else if ( key[0] == 'q'  )      //quit if 'q'  pressed
          quit = true;                  //same as "if ( event.key.keysym.sym == SDLK_q )"

    }
    SDL_Delay ( 100 );			//give up some computing time
  }

  SDL_WaitThread ( consumer, NULL );	//wait for child threads to finish
  SDL_WaitThread ( producer, NULL );
  printf("Setting video mode successful!\n");
  for ( int i = 0; i < 4; ++i )
    free ( buf[i] );            	//free the allocated memory
  if ( decode_flag == ENCODE )		
    outputs->closeOutput();		//close output file

  //need to update nframes in compressed file if not whole input file encoded
  if ( decode_flag == ENCODE && videoFramesRead < numVideoFrames ) { 	
    vheader.nframes = videoFramesRead;
    FILE *fpo = fopen ( outfilename, "r+" );
    if ( fpo == NULL ) {
      printf ("Error opening %s \n", outfilename );
      return 1;
    }
    fwrite ( (void *) &vheader, 1, sizeof ( vheader ), fpo );
    fclose ( fpo );
  } 
  return 0;
}