A simple video codec.
/*
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;
}