#include #include #include #include #include #include #include #include #include #include #include "filename.h" #include "vfapi_read.h" #include "vfw_read.h" #include "check_block.h" #include "compressor.h" #include "bgr24.h" #define DEFAULT_RATE 3000 #define DEFAULT_KEY 8 #define DEFAULT_CC 75 #define DEFAULT_DIFF 9 #define DEFAULT_PIXEL 28 #define MAX_RATE 60000 #define MAX_KEY 9999 #define MAX_CC 100 #define MAX_DIFF 64 #define MAX_PIXEL 64 #define MIN_RATE 1 #define MIN_KEY 1 #define MIN_CC 1 #define MIN_DIFF 4 #define MIN_PIXEL 4 #define DESC "\nAuto 2 pass MsPEG4 compressor - \"Spaghetti 21st Type-B\" (2002, 1/25)\n" #define COPYRIGHT " (C) Tatibana Advanced Research Institute & MARUMO\n\n" #define LOG_HEADER " frame size key\n" #define LOG_FORMAT "%6d %6d %s\n" #define LOG_FORMAT_SCENE_CHANGE "%6d %6d %s - scene change (size %6d)\n" #define LOG_FORMAT_BAD_BLOCK "%6d %6d %s - bad block (X:%4d, Y:%4d)\n" typedef struct { /* codec configuration */ FOURCC version; /* codec version */ int rate; /* kbits / sec */ int key; /* seconds */ int cc; /* 1 - 100 */ /* block noise checker threshold */ int diff; int pixel; /* application setting */ int loging; int verbose; int bad_block_report; int scene_change_detector; /* * this option sets the limit of delta_frame size. * max_size is calcurated by the following equation. * * max_size = (width * height / 64) * scene_change_detector; * */ int expand_frame_rate; /* * this option indicates 60 fps output. * * ex. * 24fps to 60fps (o o o o -> oxxoxoxxox) * 30fps to 60fps (o o o o o -> oxoxoxoxox) * [ o - frame, x - null frame ] */ } options; typedef struct { PAVIFILE file; PAVISTREAM video; AVISTREAMINFO avi_format; LPBITMAPINFO original_format; LPBITMAPINFO compress_format; COMPRESSOR encoder; LPVOID encoded_data; LPVOID decoded_data; int key; size_t size; HIC decoder; unsigned int delta_frame_max_size; int expand_frame_rate; }MPEG4_OUT; #define EXPAND_FRAME_RATE_NO 0 #define EXPAND_FRAME_RATE_24_to_60 1 #define EXPAND_FRAME_RATE_30_to_60 2 typedef struct { void * this; int (* get_frame)(void *, LPVOID *, LPBITMAPINFOHEADER *); void (* release)(void *); unsigned int max_frame; }VIDEO_INPUT; void print_usage(char *name, FILE *out); void print_option(FILE *out); int parse_option(int argc, char **argv, options *opt); void check_option(options *opt); void print_time(time_t time, FILE *out); time_t calc_rest_time(int current, int all, time_t start); void print_size(size_t bytes, FILE *out); int initialize_mpeg4out(MPEG4_OUT *p); int open_mpeg4out(char *filename, MPEG4_OUT *p, AVISTREAMINFO *avi_format, BITMAPINFOHEADER *frame_format, options *opt); void compress_force_key_frame(MPEG4_OUT *p, LPVOID data); int close_mpeg4out(MPEG4_OUT *p); LPBITMAPINFO copy_bitmap_info(LPBITMAPINFOHEADER in); void bad_block_report(LPVOID original, LPVOID result, BITMAPINFOHEADER *format, int x, int y, int diff, int pixel, int frame); void m4c(char *infile, char *outfile, options *opt); void _cdecl sigint_handler(int sig); int main(int argc, char **argv) { int n; options opt; n = parse_option(argc, argv, &opt); check_option(&opt); if((argc - n) < 2){ print_usage(argv[0], stderr); if(argc != 1){ fprintf(stderr, "\nERROR - input file & output file are required\n"); } exit(EXIT_FAILURE); } AVIFileInit(); /* initialize VfW library */ m4c(argv[argc-2], argv[argc-1], &opt); AVIFileExit(); /* release resources */ return EXIT_SUCCESS; } void print_usage(char *name, FILE *out) { fprintf(out, DESC); fprintf(out, COPYRIGHT); fprintf(out, "USAGE: %s [options] in.avi[/aup/tpr/.. ] out.avi\n", read_filename(name)); print_option(out); } void print_option(FILE *out) { fprintf(out, "options:\n"); fprintf(out, " compressor configuration:\n"); fprintf(out, " -v C select codec, default: 2 (1:MPG4,2:MP42,3:MP43,L:DIV3,F:DIV4)\n"); fprintf(out, " -r N bit rate, default: %d kbps\n", DEFAULT_RATE); fprintf(out, " -k N key frame, default: %d sec\n", DEFAULT_KEY); fprintf(out, " -c N compression ctrl, default: %d %%\n", DEFAULT_CC); fprintf(out, " block noise detector:\n"); fprintf(out, " -d N difference, default: %2d (range: %d - %d)\n", DEFAULT_DIFF, MIN_DIFF, MAX_DIFF); fprintf(out, " -p N pixel, default: %2d (range: %d - %d)\n", DEFAULT_PIXEL, MIN_PIXEL, MAX_PIXEL); fprintf(out, " application setting:\n"); fprintf(out, " -s N scene change detect, default: 0 (0:NO,8:HIGH,12:MID,16:LOW,.. )\n"); fprintf(out, " -b analyze bad block\n"); fprintf(out, " -l loging\n"); fprintf(out, " -e expand frame rate (60fps output)\n"); fprintf(out, " -q quiet\n"); fprintf(out, " -h print this message\n"); } int parse_option(int argc, char **argv, options *opt) { int i,j,n; int c; opt->version = mmioFOURCC('M', 'P', '4', '2'); opt->rate = DEFAULT_RATE; opt->key = DEFAULT_KEY; opt->cc = DEFAULT_CC; opt->loging = 0; opt->verbose = 1; opt->scene_change_detector = 0; opt->bad_block_report = 0; opt->expand_frame_rate = 0; opt->diff = DEFAULT_DIFF; opt->pixel = DEFAULT_PIXEL; for(i=1;i1){ c = argv[i][j+1] & 0xFF; j += 2; }else{ c = argv[i+1][0] & 0xFF; i++; } switch(c){ case '1': opt->version = mmioFOURCC('M', 'P', 'G', '4'); break; case '2': opt->version = mmioFOURCC('M', 'P', '4', '2'); break; case '3': opt->version = mmioFOURCC('M', 'P', '4', '3'); break; case 'l': case 'L': opt->version = mmioFOURCC('D', 'I', 'V', '3'); break; case 'f': case 'F': opt->version = mmioFOURCC('D', 'I', 'V', '4'); break; default: opt->version = mmioFOURCC('M', 'P', '4', '2'); } break; case 'r': if(n-j>1){ sscanf(argv[i]+j+1, "%d", &(opt->rate)); j = n; }else{ sscanf(argv[i+1], "%d", &(opt->rate)); i++; } break; case 'k': if(n-j>1){ sscanf(argv[i]+j+1, "%d", &(opt->key)); j = n; }else{ sscanf(argv[i+1], "%d", &(opt->key)); i++; } break; case 'c': if(n-j>1){ sscanf(argv[i]+j+1, "%d", &(opt->cc)); j = n; }else{ sscanf(argv[i+1], "%d", &(opt->cc)); i++; } break; case 'd': if(n-j>1){ sscanf(argv[i]+j+1, "%d", &(opt->diff)); j = n; }else{ sscanf(argv[i+1], "%d", &(opt->diff)); i++; } break; case 'p': if(n-j>1){ sscanf(argv[i]+j+1, "%d", &(opt->pixel)); j = n; }else{ sscanf(argv[i+1], "%d", &(opt->pixel)); i++; } break; case 'l': opt->loging = 1; break; case 'e': opt->expand_frame_rate = 1; break; case 'q': opt->verbose = 0; break; case 'h': print_usage(argv[0], stderr); exit(EXIT_SUCCESS); case 's': if(n-j>1){ sscanf(argv[i]+j+1, "%d", &(opt->scene_change_detector)); j = n; }else{ sscanf(argv[i+1], "%d", &(opt->scene_change_detector)); i++; } break; case 'b': opt->bad_block_report = 1; break; default: fprintf(stderr, "ERROR - Invalid option \"%c\" specified\n", argv[i][j]); print_option(stderr); exit(EXIT_FAILURE); } } } return i; } void check_option(options *opt) { if(opt->rate > MAX_RATE){ opt->rate = MAX_RATE; }else if(opt->rate < MIN_RATE){ opt->rate = MIN_RATE; } if(opt->key > MAX_KEY){ opt->key = MAX_KEY; }else if(opt->key < MIN_KEY){ opt->key = MIN_KEY; } if(opt->cc > MAX_CC){ opt->cc = MAX_CC; }else if(opt->cc < MIN_CC){ opt->cc = MIN_CC; } if(opt->diff > MAX_DIFF){ opt->diff = MAX_DIFF; }else if(opt->diff < MIN_DIFF){ opt->diff = MIN_DIFF; } if(opt->pixel > MAX_PIXEL){ opt->pixel = MAX_PIXEL; }else if(opt->pixel < MIN_PIXEL){ opt->pixel = MIN_PIXEL; } return; } int initialize_mpeg4out(MPEG4_OUT *p) { memset(p, 0, sizeof(MPEG4_OUT)); return 1; } int open_mpeg4out(char *filename, MPEG4_OUT *p, AVISTREAMINFO *avi_format, BITMAPINFOHEADER *frame_format, options *opt) { int n; FPS fps; fps.rate = avi_format->dwRate; fps.scale = avi_format->dwScale; initialize_mpeg4out(p); p->avi_format = *avi_format; p->avi_format.fccHandler = opt->version; p->original_format = copy_bitmap_info(frame_format); p->compress_format = copy_bitmap_info(frame_format); p->compress_format->bmiHeader.biCompression = opt->version; if(! open_compressor(&(p->encoder), &(p->original_format->bmiHeader), opt->version, opt->rate, opt->key, opt->cc, &fps) ){ return 0; } DeleteFile(filename); if(AVIFileOpen(&(p->file), filename, OF_CREATE|OF_WRITE|OF_SHARE_EXCLUSIVE, NULL)){ close_compressor(&(p->encoder)); return 0; } p->expand_frame_rate = EXPAND_FRAME_RATE_NO; if(opt->expand_frame_rate){ n = (fps.rate+fps.scale-1)/fps.scale; if( (n>23) && (n<25) ){ p->expand_frame_rate = EXPAND_FRAME_RATE_24_to_60; p->avi_format.dwRate *= 5; p->avi_format.dwScale *= 2; }else if( (n>29) && (n<31) ){ p->expand_frame_rate = EXPAND_FRAME_RATE_30_to_60; p->avi_format.dwRate *= 2; } } AVIFileCreateStream(p->file, &(p->video), &(p->avi_format)); AVIStreamSetFormat(p->video, 0, &(p->compress_format->bmiHeader), p->compress_format->bmiHeader.biSize); p->decoder = ICOpen(ICTYPE_VIDEO, p->avi_format.fccHandler, ICMODE_DECOMPRESS); ICDecompressBegin(p->decoder, p->compress_format, p->original_format); p->decoded_data = malloc(frame_format->biSizeImage); p->encoded_data = malloc(frame_format->biSizeImage); p->delta_frame_max_size = (frame_format->biWidth * frame_format->biHeight / 64) * opt->scene_change_detector; return 1; } LPBITMAPINFO copy_bitmap_info(LPBITMAPINFOHEADER in) { LPBITMAPINFO r; r = malloc(in->biSize+in->biSizeImage); memcpy(r,in, in->biSize); return r; } void compress_force_key_frame(MPEG4_OUT *p, LPVOID data) { start_compression(&(p->encoder)); compress(data, p->encoded_data, &(p->encoder), &(p->key), &(p->size)); } int close_mpeg4out(MPEG4_OUT *p) { if(p->video){ AVIStreamRelease(p->video); } if(p->file){ AVIFileRelease(p->file); } if(p->original_format){ free(p->original_format); p->original_format = NULL; } if(p->compress_format){ free(p->compress_format); p->compress_format = NULL; } if(p->encoded_data){ free(p->encoded_data); p->encoded_data = 0; } if(p->encoder.driver){ close_compressor(&(p->encoder)); } if(p->decoded_data){ free(p->decoded_data); p->decoded_data = NULL; } if(p->decoder){ ICDecompressEnd(p->decoder); ICClose(p->decoder); } return 1; } void print_time(time_t time, FILE *out) { int dd; int hh; int mm; int ss; ss = time % 60; mm = time / 60 % 60; hh = time / 60 / 60 % 24; dd = time / 60 / 60 / 24; if(dd){ fprintf(out, "%02d:%02d:%02d", dd, hh, mm); }else{ fprintf(out, "%02d:%02d:%02d", hh, mm, ss); } } time_t calc_rest_time(int current, int all, time_t start) { time_t now; time_t elapsed; time_t rest; now = time(NULL); elapsed = now - start; if(current == 0){ current = 1; } rest = (all - current) * elapsed / current; return rest; } void print_size(size_t bytes, FILE *out) { size_t g,m,k,b; b = bytes % 1024; k = bytes / 1024 % 1024; m = bytes / 1024 / 1024 % 1024; g = bytes / 1024 / 1024 / 1024; if(g){ fprintf(out, "%d.%03d G", g, m * 1000 / 1024); }else if(m){ if(m < 10){ fprintf(out, "%1d.%03d M", m, k * 1000 / 1024); }else if(m < 100){ fprintf(out, "%2d.%02d M", m, k * 100 / 1024); }else if(m < 1000){ fprintf(out, "%3d.%01d M", m, k * 10 / 1024); }else{ fprintf(out, "%4d. M", m); } }else if(k){ if(k < 10){ fprintf(out, "%1d.%03d K", k, b * 1000 / 1024); }else if(k < 100){ fprintf(out, "%2d.%02d K", k, b * 100 / 1024); }else if(k < 1000){ fprintf(out, "%3d.%01d K", k, b * 10 / 1024); }else{ fprintf(out, "%4d. K", k); } }else{ fprintf(out, "%4d ", b); } } void bad_block_report(LPVOID original, LPVOID result, BITMAPINFOHEADER *format, int x, int y, int diff, int pixel, int frame) { int i,j; int d,h; unsigned char *in, *out, *p1, *p2; IMAGE *img; int da[256]; char path[FILENAME_MAX]; static COLOR white = { 255, 255, 255, }; static COLOR gray = { 128, 128, 128, }; static COLOR red = { 0, 0, 255, }; static COLOR blue = { 255, 0, 0, }; img = new_image(372, 160); fill_rectangle(img, 0, 0, 372, 160, &white); /* 2x copy original block */ in = (unsigned char *)original; in += format->biWidth * 3 * (y-8) + (x-8) * 3; out = img->data + 372 * (24+48+16) * 3 + 26 * 3; for(i=0;i<24;i++){ for(j=0;j<24;j++){ if( (y-8+i < 0) || (y-8+i >= format->biHeight) || (x-8+j < 0) || (x-8+j >= format->biWidth) ){ out[j*6+0] = 128; out[j*6+1] = 128; out[j*6+2] = 128; out[j*6+3] = 128; out[j*6+4] = 128; out[j*6+5] = 128; out[372*3+j*6+0] = 128; out[372*3+j*6+1] = 128; out[372*3+j*6+2] = 128; out[372*3+j*6+3] = 128; out[372*3+j*6+4] = 128; out[372*3+j*6+5] = 128; }else{ out[j*6+0] = in[j*3+0]; out[j*6+1] = in[j*3+1]; out[j*6+2] = in[j*3+2]; out[j*6+3] = in[j*3+0]; out[j*6+4] = in[j*3+1]; out[j*6+5] = in[j*3+2]; out[372*3+j*6+0] = in[j*3+0]; out[372*3+j*6+1] = in[j*3+1]; out[372*3+j*6+2] = in[j*3+2]; out[372*3+j*6+3] = in[j*3+0]; out[372*3+j*6+4] = in[j*3+1]; out[372*3+j*6+5] = in[j*3+2]; } } out += 372*6; in += format->biWidth * 3; } /* copy bad block */ in = (unsigned char *)result; in += format->biWidth * 3 * (y-8) + (x-8) * 3; out = img->data + 372 * 24 * 3 + 26 * 3; for(i=0;i<24;i++){ for(j=0;j<24;j++){ if( (y-8+i < 0) || (y-8+i >= format->biHeight) || (x-8+j < 0) || (x-8+j >= format->biWidth) ){ out[j*6+0] = 128; out[j*6+1] = 128; out[j*6+2] = 128; out[j*6+3] = 128; out[j*6+4] = 128; out[j*6+5] = 128; out[372*3+j*6+0] = 128; out[372*3+j*6+1] = 128; out[372*3+j*6+2] = 128; out[372*3+j*6+3] = 128; out[372*3+j*6+4] = 128; out[372*3+j*6+5] = 128; }else{ out[j*6+0] = in[j*3+0]; out[j*6+1] = in[j*3+1]; out[j*6+2] = in[j*3+2]; out[j*6+3] = in[j*3+0]; out[j*6+4] = in[j*3+1]; out[j*6+5] = in[j*3+2]; out[372*3+j*6+0] = in[j*3+0]; out[372*3+j*6+1] = in[j*3+1]; out[372*3+j*6+2] = in[j*3+2]; out[372*3+j*6+3] = in[j*3+0]; out[372*3+j*6+4] = in[j*3+1]; out[372*3+j*6+5] = in[j*3+2]; } } out += 372*6; in += format->biWidth * 3; } /* analyze bad block */ p1 = (unsigned char *)original; p2 = (unsigned char *)result; p1 += format->biWidth * 3 * y + x * 3; p2 += format->biWidth * 3 * y + x * 3; memset(da, 0, 256*sizeof(int)); for(i=0;i<8;i++){ for(j=0;j<8;j++){ d = RGB_to_Y(p1+j*3) - RGB_to_Y(p2+j*3); d = abs(d); da[d] += 1; } p1 += format->biWidth * 3; p2 += format->biWidth * 3; } /* create graph */ fill_rectangle(img, 100, 16, 256, 128, &gray); h = 0; for(i=255;i>63;i--){ h += da[i]; } for(i=63;i>=0;i--){ h += da[i]; fill_rectangle(img, 100+i*4, 16, 4, h*2, &blue); } draw_horizontal_line(img, 100, 16+pixel*2, 256, &red); draw_vertical_line(img, 100+diff*4, 16, 128, &red); /* draw scale */ for(i=1;i<64;i++){ if(i%10){ draw_vertical_line(img, 100+i*4, 16, 3, &white); }else{ draw_vertical_line(img, 100+i*4, 16, 6, &white); } } for(i=1;i<32;i++){ if(i%5){ draw_horizontal_line(img, 100, 16+i*4, 3, &white); }else{ draw_horizontal_line(img, 100, 16+i*4, 6, &white); } } sprintf(path, "%08d.bmp", frame); store_bmp(img, path); delete_image(img); } /******************************************************************* global variables for SIGINT handling *******************************************************************/ VIDEO_INPUT in; MPEG4_OUT out; /******************************************************************* global variables for SIGINT handling *******************************************************************/ void m4c(char *infile, char *outfile, options *opt) { unsigned int i,j,w; int is_vfapi; VFAPI plugin; AVI_IN avi_in; LPBITMAPINFOHEADER format; LPVOID data; FILE *log; char *logfile; time_t start; time_t rest; size_t sum; size_t total; if(check_sfx(infile, ".avi")){ if(! open_file_by_vfw(infile, &avi_in) ){ fprintf(stderr, "Error - Can't open %s\n", infile); exit(EXIT_FAILURE); } in.this = (void *)(& avi_in); in.get_frame = read_frame_vfw; in.release = close_avi; in.max_frame = avi_in.avi_format.dwLength; is_vfapi = 0; }else{ if(! open_file_by_vfapi(infile, &plugin) ){ fprintf(stderr, "Error - Can't open %s\n", infile); exit(EXIT_FAILURE); } in.this = (void *)(& plugin); in.get_frame = read_frame_vfapi; in.release = close_plugin; if(plugin.video_info.dwLengthH){ in.max_frame = INT_MAX; }else{ in.max_frame = plugin.video_info.dwLengthL; } is_vfapi = 1; } if(is_vfapi){ w = open_mpeg4out(outfile, &out, &(plugin.avi_format), plugin.frame_format, opt); }else{ w = open_mpeg4out(outfile, &out, &(avi_in.avi_format), avi_in.frame_format, opt); } if(! w){ fprintf(stderr, "ERROR - Can't open %s\n", outfile); in.release(in.this); exit(EXIT_FAILURE); } if(opt->loging){ logfile = change_sfx(outfile, ".log"); log = fopen(logfile, "w"); fprintf(log, "[%s]\n", outfile); fprintf(log, "rate: %d, key: %d, cc: %d, pixel: %d, diff: %d\n", opt->rate, opt->key, opt->cc, opt->pixel, opt->diff); fprintf(log, LOG_HEADER); } start = time(NULL); sum = 0; signal(SIGINT, sigint_handler); for(i=0,j=0;i out.delta_frame_max_size) ){ oversize = out.size; compress_force_key_frame(&out, data); if(opt->loging){ fprintf(log, LOG_FORMAT_SCENE_CHANGE, i, out.size, "***", oversize); } }else{ ICDecompress(out.decoder, ICDECOMPRESS_NOTKEYFRAME, &(out.compress_format->bmiHeader), out.encoded_data, format, out.decoded_data); address = check_block(data, out.decoded_data, format, opt->diff, opt->pixel); if(address){ compress_force_key_frame(&out, data); if(opt->loging){ fprintf(log, LOG_FORMAT_BAD_BLOCK, i, out.size, "***", (address >> 2) & 0xFFFF, format->biHeight - (address >> 18) - 8); } if(opt->bad_block_report){ bad_block_report(data, out.decoded_data, format, (address >> 2) & 0xFFFF, (address >> 18), opt->diff, opt->pixel, i); } }else{ if(opt->loging){ fprintf(log, LOG_FORMAT, i, out.size, ""); } } } }else{ if(opt->loging){ fprintf(log, LOG_FORMAT, i, out.size, "***"); } } if(out.key){ ICDecompressBegin(out.decoder, out.compress_format, out.original_format); ICDecompress(out.decoder, 0, &(out.compress_format->bmiHeader), out.encoded_data, format, out.decoded_data); } AVIStreamWrite(out.video, j, 1, out.encoded_data, out.size, out.key ? AVIIF_KEYFRAME : 0, NULL, NULL); j+=1; switch(out.expand_frame_rate){ case EXPAND_FRAME_RATE_24_to_60: if(i&1){ AVIStreamWrite(out.video, j, 1, NULL, 0, 0, NULL, NULL); j+=1; AVIStreamWrite(out.video, j, 1, NULL, 0, 0, NULL, NULL); j+=1; }else{ AVIStreamWrite(out.video, j, 1, NULL, 0, 0, NULL, NULL); j+=1; } break; case EXPAND_FRAME_RATE_30_to_60: AVIStreamWrite(out.video, j, 1, NULL, 0, 0, NULL, NULL); j+=1; break; } if(opt->verbose){ fprintf(stderr, "\rframe: %6d/%6d, rest time: ", i+1,in.max_frame); rest = calc_rest_time(i+1, in.max_frame, start); print_time(rest, stderr); fprintf(stderr, ", estimate size: "); sum += out.size; total = sum / (i+1) * in.max_frame; print_size(total, stderr); fflush(stderr); } } if(opt->loging){ fclose(log); free(logfile); } close_mpeg4out(&out); in.release(in.this); signal(SIGINT, SIG_DFL); fprintf(stderr, "\nfinish!! total time: "); print_time(time(NULL) - start, stderr); fprintf(stderr, "\n"); } void _cdecl sigint_handler(int sig) { close_mpeg4out(&out); in.release(in.this); exit(EXIT_FAILURE); }