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

Sample program for converting RGB to YCbCr to DCT coefficients and vice versa.

common.h
rgb_ybr.h
dct_video.h
encode.h
dctplayer.cpp
encode.cpp
dct_video.cpp
rgb_ybr.cpp
Makefile
sample_video.raw



dctplayer.cpp:
/*
  dctplayer.cpp
  Hard-coded program to illustrate the conversion from RGB to 4:2:0 YCbCr then to DCT coefficients
  and vice versa.
  Assume that our video is 320x240 8-bit data.  
  It converts raw RGB data of "sample_video.raw" to DCT coefficients to be saved in "sample_video.dct" 
  and plays the video.
  Or if an argument "d" is provided, it reads DCT coefficients from "sample_video.dct", applies
  IDCT to recover the YCbCr sample blocks, converts YCbCr to RGB data
  and plays the video.
  See 'http://www.webkinesia.com/games/vcompress4.php'
*/
#include <SDL/SDL.h>
#include <SDL/SDL_thread.h>
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include "common.h"
#include "encode.h"
#include "rgb_ybr.h"
#include "dct_video.h"


short dct_flag = 0;		//0: read RGB file, perform YCbCr and DCT transforms, and play video; 
				//1: read DCT coefficients, reverse conversions to play video
FILE *fpi, *fpo;		
short width = 320;		//image width
short height = 240;		//image height	
unsigned long frameSize;	//size of one frame
unsigned long head=0, tail=0;	//for synchronization of producer and consumer threads
bool quit = false;		//quit program if set to true
char *buf[4];			//ring buffer to hold four frames of data

//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 ( 30 );		//sleep for 30 ms
      continue;
    }
    //consumes the data
    if ( !dct_flag )		//convert RGB data to YCbCr and save it
      encode (buf[head%4],fpo);	//encode and save there
				//if dct_flag == 1, we just play the data in buf[]
    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;
}
 
int get_dct_block ( int Y[] )		//read in one DCT block
{
  short temp[64];

  if ( !fread ( temp, 2, 64, fpi ) )
     return 0;
 
  for ( int i = 0; i < 64; ++i )
    Y[i] = ( int ) temp[i];

  return 1;
}

/*
  Get DCT data from file pointed by fpi; convert DCT coefficients to YCbcr and put the 
  four 8x8 Y sample blocks, one 8x8 Cb sample block and one 8x8 Cr sample block into a 
  struct of YCbCr_MACRO.
  Return: number of bytes read from file and put in YCbCr_MACRO struct.
*/
int get_ybrblocks( YCbCr_MACRO *ycbcr_macro )
{
  short r, row, col, i, j, k, n, block, *py;
  short c;
  int 	Y[64], X[64];		//for IDCT 
  //short Y[64], X[64];

  n = 0;
  //read data from file, perform IDCT and put them in four 8x8 Y sample blocks
  for ( block = 0; block < 4; block++ ) {
  if ( !get_dct_block ( Y ) )	//read in one DCT block
      return 0;
    idct ( Y, X );		//perform IDCT, output in X
    k = 0;
    if ( block < 2 )
      py = ( short * ) &ycbcr_macro->Y + 8*block;        	//points to beginning of block
    else
      py = (short *)&ycbcr_macro->Y + 128 + 8*(block-2);	//points to beginning of block
    for ( i = 0; i < 8; i++ ) {         //one sample-block
      if ( i > 0 ) py += 16;        	//advance py by 16 ( length of one row of macroblock )
      for ( j = 0; j < 8; j++ ) {
	*(py+j) = X[k++];		//put sample value in macroblock
	 n++;
      } //for j
    } //for i
  } //for block

  if ( !get_dct_block ( Y ) )		//read in one DCT block
    return 0;
  idct (Y, X );				//perform IDCT, output in X
  k = 0;
  for ( i = 0; i < 8; ++i ) {
    for ( j = 0; j < 8; ++j ) {
      ycbcr_macro->Cb[k] = X[k];	//put Cb sample value in macro block
      k++;	
      n++;
    }
  } 
  
  //now do that for 8x8 Cr block
  if ( !get_dct_block ( Y ) )		//read in one DCT block
    return 0;
  idct (Y, X );				//perform IDCT, output in X
  k = 0;
  for ( i = 0; i < 8; ++i ) {
    for ( j = 0; j < 8; ++j ) {
      ycbcr_macro->Cr[k] = X[k];	//put Cr sample value in macro block
      k++;	
      n++;
    }
  }
  return n;				//number of bytes read
}

/*
  Convert a YCbCr frame to RGB frame.
*/
int decode_ybrFrame ( char *image )
{
  short r, row, col, i, j, k, block, *py;
  int n;
  RGB *p, macro16x16[256];
  YCbCr_MACRO ycbcr_macro;

  for ( row = 0; row < height; row += 16 ) {
    for ( col = 0; col < width; col += 16 ) {
      n = get_ybrblocks( &ycbcr_macro );
      if ( n <= 0 ) return n;
      ycbcr2macroblock( &ycbcr_macro, macro16x16 );
      p = ( RGB *) image  +  ( row * width + col );     //points to beginning of macroblock
      r = 0;
      for ( i = 0; i < 16; ++i ) {
        for ( j = 0; j < 16; ++j ) {
          *p++ =  macro16x16[r++];
        }
        p += ( width - 16 );          //points to next row within macroblock
      }
    } //for col
  }  //for row
  return n;
}

int decode_frame ( char *image )
{
  int n;
  
  n = fread ( image, 1, frameSize, fpi );

  return n;
}

//Producer
int decoder ( void *data )
{
  int n;
  char *image;

  while ( !quit ) {
     if ( tail >= head + 4 ) {	//buffer full
	SDL_Delay ( 30 );	//sleep for 30 ms ( not the best method, better use
				//semaphore, let consumer wake you up
	continue;
     }
     //produce data
     image = buf[tail%4];
     if ( !dct_flag )
        n = decode_frame ( image );//decoded data put in image[]
     else
   	n = decode_ybrFrame ( image );
     if ( n <= 0 )
	quit = true; 
     else
	tail++;
  } //while
  return 0;
}

int main( int argc, char *argv[] )
{   
  SDL_Surface *screen;
  frameSize = width * height * 3;
  SDL_Thread *producer, *consumer;
  SDL_Event event;
  int status;
  char *key;


  if ( argc > 1 && ( strcmp ( argv[1], "d" ) == 0 ) ) {	//play from DCT coef file
    dct_flag = 1;
    fpi = fopen ( "sample_video.dct", "rb" );		//input of DCT data
    if ( fpi == NULL ) {
      printf("\nError opening file \"sample_video.dct\" for input\n" );
      return 1;
    }
  } else { 						//play from RGB file and convert to YCbCr
    dct_flag = 0;
    fpi = fopen ( "sample_video.raw", "rb" );		//input of RGB data		
    if ( fpi == NULL ) {
      printf("\nError opening file \"sample_video.raw\" for input\n" );
      return 1;
    }
    fpo = fopen ( "sample_video.dct", "wb" );		//output of DCT coeff data
    if ( fpo == NULL ) {
      printf("\nError opening file \"sample_video.dct\" for output\n" );
      return 1; 
    }
  }
  //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 320 x 240 with 24-bit pixels
  screen = SDL_SetVideoMode(width, height, 24, SDL_SWSURFACE);
  if ( screen == NULL ) {
        fprintf(stderr, "Unable to set 320x240 video: %s\n", SDL_GetError());
        exit(1);
  }
  for ( int i = 0; i < 4; ++i ) {
    buf[i] = ( char * ) malloc ( frameSize );
    assert ( buf[i] );
  }
  head = tail = 0;
  consumer = SDL_CreateThread ( player, screen );
  producer = SDL_CreateThread ( decoder, ( void * ) "decdoing" );
  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 CPU time
  }

  SDL_WaitThread ( consumer, NULL );	//wait for child threads to finish
  SDL_WaitThread ( producer, NULL );
  
  return 0;
}