Hi all, I'm back on work on transcode, slowly as usual because the spare time always too low. Stuck on my ~FIFO[1] TODO, I've recently merged yait code.
I've also performed a little deeper code review on filter_yait.c, and I like ot submit it here (especially to Allan's attention) before to merge it on CVS. Please check out attached patch[0], and point out my mistakes. There is a few comments and some tiny code logic changes (nothing serious of course, just initialization reorder, some code factorization and constantization and stuff like that). Please let me know if I broke something. I can't test it new version now or soon due to lack of NTSC material and, most important, time. A review and STYLE-ification of tcyait will be follow into near future. A word about of STYLE. I know that can be annoying (everyone has it's own style and everyone wants to keep it), but I strongly believe that having a consistent codebase helps significantly in keeping codebase safe and fun to hack in. So submitted code -like yait- will be STYLE-ificated[2]. I've already seen too much horrors in the past, even (especially?) into our own codebase :) Thoughts? +++ [0] yeah, it's big. That's mainly due to tab removal. [1] Please bug me if a task starts to starve (i.e., recent configure patches to Os X, that was my bad) [2] Resistance is futile, you will be STYLE-ificated :) Bests, -- Francesco Romani
Index: filter/filter_yait.c =================================================================== RCS file: /cvstc/transcode/filter/filter_yait.c,v retrieving revision 1.1 diff -u -r1.1 filter_yait.c --- filter/filter_yait.c 26 May 2007 15:52:10 -0000 1.1 +++ filter/filter_yait.c 27 May 2007 09:28:47 -0000 @@ -20,10 +20,10 @@ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ -#define MOD_NAME "filter_yait.so" -#define MOD_VERSION "v0.1 (2007-02-14)" -#define MOD_CAP "Yet Another Inverse Telecine filter" -#define MOD_AUTHOR "Allan Snider" +#define MOD_NAME "filter_yait.so" +#define MOD_VERSION "v0.2 (2007-05-26)" +#define MOD_CAP "Yet Another Inverse Telecine filter" +#define MOD_AUTHOR "Allan Snider" #include "transcode.h" #include "filter.h" @@ -36,729 +36,686 @@ /* - * yait: - * Yet Another Inverse Telecine filter. + * yait: + * Yet Another Inverse Telecine filter. * - * Usage: - * -J yait=log[=file] (-y null) - * -J yait=ops[=file] - * - * Description: - * - * This filter is designed specifically to handle mixed progressive and NTSC - * telecined data (2:3 pulldown), converting from NTSC_VIDEO (29.97 fps) to NTSC_FILM - * (23.976 fps). It uses row save and copy operations to reconstruct progressive - * frames. It is provided as an alternative to the -J ivtc,32detect,decimate method. - * - * For those who don't care how much cpu is used but are interested only - * in trying to achieve the best quality rendering, then read on. If not, then stop - * reading right now, as this filter requires a complete separate pass on the video - * stream and an external analysis tool to be run. - * - * The main advantage of using a separate pass is that it provides a much - * larger window of frames to examine when deciding what frames need to be dropped - * or de-interlaced. The video stream is read at 30 fps (--hard_fps). Duplicate - * frames are inserted by the demuxer to keep the frame rate at 30 when progressive - * data is encountered. These frames can appear quite early or late, far beyond the - * five frame local window used by ivtc, etc. This approach allows drop frames to - * be credited, and debited, (up to a point), making better drop frame choices. The - * result is a (noticeably) smoother video. - * - * Another advantage of using a large frame window is to provide context - * for determining interlace patterns. Local interlace patterns (eg. a 5 frame - * window) can sometimes be impossible to determine. When able to look ahead or - * behind for existing patterns, usually the correct pattern can be inherited. - * - * The filter guarantees one drop frame per 5 frames. No more, no less. - * - * Using the filter: - * - * Pass 1: - * - * -J yait=log -y null - * - * The first pass is used only to generate row (even/odd) delta information. - * This is written as a text log file (called yait.log by default). (The - * file name can be specified using yait=log=file). This is the only data - * required by this pass, so no video (or audio) frames need to be encoded. - * You do need to specify the demuxer_sync method however. (It must be 2). - * - * Alternatively (for debug purposes), you may want to generate a frame - * labeled video file, then compare the yait analysis to the original video. - * In this case use something like: - * -H 10 -x vob -i ... - * --export_fps 0,4 --demuxer_sync 2 -y xvid4,null -o label.ogm - * -J yait=log -J text=frame:posdef=8 - * - * Running the tcyait tool: - * - * Pass 1 created a yait.log file. The analysis tool 'tcyait' is then run - * which reads the log file and determines which areas are telecined and - * progressive and detects the telecine patterns. A yait frame operations - * file is then written (yait.ops by default). It is a text file containing - * instructions for each frame, such as nop, save even or odd rows, copy rows, - * drop frames, or blend a frame. The usage of the tool is as follows: - * - * tcyait [-d] [-l log] [-o ops] [-m mode] - * -d Print debug information to stdout. - * -l log Specify input yait log file name [yait.log]. - * -o ops Specify output yait frame ops file name [yait.ops]. - * -m mode Specify transcode de-interlace method [3]. - * - * I typically run it as: - * tcyait -d > yait.info - * - * One could query why pass 1 doesn't just create the .ops file directly. That - * is, run the analysis at TC_FILTER_CLOSE time and save the user from having - * to run the tool directly. The main reason is that the tcyait code is alpha - * and still undergoing a lot of fine tuning. I would not want to have to - * regenerate the row delta information every time I tweaked the analysis - * portion. So, it exists as a separate tool for now. - * - * Pass 2 (and 3): - * - * -J yait=ops -y ... - * - * The second pass (or third for -R 1 and -R 2), reads the frame operations file - * generated by tcyait, (called yait.ops by default). (The file name can be - * specified using yait=ops=file). This file instructs the filter to save or - * copy rows, skip frames, or de-interlace frames, and causes the pre filtering - * to reduce the frame rate to 24 fps. Hence, you must specify the export fps - * as 24, or will get truncated audio, (ie. --export_fps 24,1). The frame - * sequence seen by the filter must match exactly what pass 1 saw. That is, you - * cannot specify a frame range or audio track in pass 1, but not pass 2, and - * visa versa. Here is an example invocation: - * - * transcode -H 10 -a 0 -x vob -i ... -w 1800,50 -b 192,0,0 -Z ... - * -R 1 -y xvid4,null --demuxer_sync 2 --export_fps 24,1 - * -J yait=ops --progress_rate 25 - * - * transcode -H 10 -a 0 -x vob -i ... -w 1800,50 -b 192,0,0 -Z ... - * -R 2 -y xvid4 --demuxer_sync 2 --export_fps 24,1 -o ... - * -J yait=ops --progress_rate 25 - * - * The import frame rate and --hard_fps flags are forced by the filter and - * need not be specified. - * - * DISCLAIMER: - * - * This is a work in progress. For non-NTSC telecine patterns, PAL, or purely - * interlaced material, you are going to get nonsense results. Best stick to 'ivtc' or - * 'smartyuv' for those. - * - * For some video, remarkably good results were obtained. (I was quite pleased and - * hence felt obliged to distribute this). In a few cases I had video constantly switching - * frame rates, with single or small grouped telecine, both even and odd patterns, and was - * able to reconstruct the original 24 fps progressive film for the entire file, without - * blending a single frame. For others, not so good. The analysis tool can sometimes - * generate a lot of false positives for interlace detection and specify needless frame - * blending. Generally, wherever a frame blend is specified, something went wrong. I - * usually step frame by frame in the original (frame labelled) .ogm and edit/correct - * the .ops file manually. - * - * There is much work to be done still (especially documentation), but here it - * is, such as it is. - * Allan + * Usage: + * -J yait=log[=file] (-y null) + * -J yait=ops[=file] + * + * Description: + * + * This filter is designed specifically to handle mixed progressive and NTSC + * telecined data (2:3 pulldown), converting from NTSC_VIDEO (29.97 fps) to NTSC_FILM + * (23.976 fps). It uses row save and copy operations to reconstruct progressive + * frames. It is provided as an alternative to the -J ivtc,32detect,decimate method. + * + * For those who don't care how much cpu is used but are interested only + * in trying to achieve the best quality rendering, then read on. If not, then stop + * reading right now, as this filter requires a complete separate pass on the video + * stream and an external analysis tool to be run. + * + * The main advantage of using a separate pass is that it provides a much + * larger window of frames to examine when deciding what frames need to be dropped + * or de-interlaced. The video stream is read at 30 fps (--hard_fps). Duplicate + * frames are inserted by the demuxer to keep the frame rate at 30 when progressive + * data is encountered. These frames can appear quite early or late, far beyond the + * five frame local window used by ivtc, etc. This approach allows drop frames to + * be credited, and debited, (up to a point), making better drop frame choices. The + * result is a (noticeably) smoother video. + * + * Another advantage of using a large frame window is to provide context + * for determining interlace patterns. Local interlace patterns (eg. a 5 frame + * window) can sometimes be impossible to determine. When able to look ahead or + * behind for existing patterns, usually the correct pattern can be inherited. + * + * The filter guarantees one drop frame per 5 frames. No more, no less. + * + * Using the filter: + * + * Pass 1: + * + * -J yait=log -y null + * + * The first pass is used only to generate row (even/odd) delta information. + * This is written as a text log file (called yait.log by default). (The + * file name can be specified using yait=log=file). This is the only data + * required by this pass, so no video (or audio) frames need to be encoded. + * You do need to specify the demuxer_sync method however. (It must be 2). + * + * Alternatively (for debug purposes), you may want to generate a frame + * labeled video file, then compare the yait analysis to the original video. + * In this case use something like: + * -H 10 -x vob -i ... + * --export_fps 0,4 --demuxer_sync 2 -y xvid4,null -o label.ogm + * -J yait=log -J text=frame:posdef=8 + * + * Running the tcyait tool: + * + * Pass 1 created a yait.log file. The analysis tool 'tcyait' is then run + * which reads the log file and determines which areas are telecined and + * progressive and detects the telecine patterns. A yait frame operations + * file is then written (yait.ops by default). It is a text file containing + * instructions for each frame, such as nop, save even or odd rows, copy rows, + * drop frames, or blend a frame. The usage of the tool is as follows: + * + * tcyait [-d] [-l log] [-o ops] [-m mode] + * -d Print debug information to stdout. + * -l log Specify input yait log file name [yait.log]. + * -o ops Specify output yait frame ops file name [yait.ops]. + * -m mode Specify transcode de-interlace method [3]. + * + * I typically run it as: + * tcyait -d > yait.info + * + * One could query why pass 1 doesn't just create the .ops file directly. That + * is, run the analysis at TC_FILTER_CLOSE time and save the user from having + * to run the tool directly. The main reason is that the tcyait code is alpha + * and still undergoing a lot of fine tuning. I would not want to have to + * regenerate the row delta information every time I tweaked the analysis + * portion. So, it exists as a separate tool for now. + * + * Pass 2 (and 3): + * + * -J yait=ops -y ... + * + * The second pass (or third for -R 1 and -R 2), reads the frame operations file + * generated by tcyait, (called yait.ops by default). (The file name can be + * specified using yait=ops=file). This file instructs the filter to save or + * copy rows, skip frames, or de-interlace frames, and causes the pre filtering + * to reduce the frame rate to 24 fps. Hence, you must specify the export fps + * as 24, or will get truncated audio, (ie. --export_fps 24,1). The frame + * sequence seen by the filter must match exactly what pass 1 saw. That is, you + * cannot specify a frame range or audio track in pass 1, but not pass 2, and + * visa versa. Here is an example invocation: + * + * transcode -H 10 -a 0 -x vob -i ... -w 1800,50 -b 192,0,0 -Z ... + * -R 1 -y xvid4,null --demuxer_sync 2 --export_fps 24,1 + * -J yait=ops --progress_rate 25 + * + * transcode -H 10 -a 0 -x vob -i ... -w 1800,50 -b 192,0,0 -Z ... + * -R 2 -y xvid4 --demuxer_sync 2 --export_fps 24,1 -o ... + * -J yait=ops --progress_rate 25 + * + * The import frame rate and --hard_fps flags are forced by the filter and + * need not be specified. + * + * DISCLAIMER: + * + * This is a work in progress. For non-NTSC telecine patterns, PAL, or purely + * interlaced material, you are going to get nonsense results. Best stick to 'ivtc' or + * 'smartyuv' for those. + * + * For some video, remarkably good results were obtained. (I was quite pleased and + * hence felt obliged to distribute this). In a few cases I had video constantly switching + * frame rates, with single or small grouped telecine, both even and odd patterns, and was + * able to reconstruct the original 24 fps progressive film for the entire file, without + * blending a single frame. For others, not so good. The analysis tool can sometimes + * generate a lot of false positives for interlace detection and specify needless frame + * blending. Generally, wherever a frame blend is specified, something went wrong. I + * usually step frame by frame in the original (frame labelled) .ogm and edit/correct + * the .ops file manually. + * + * There is much work to be done still (especially documentation), but here it + * is, such as it is. + * Allan */ /* - * Defines: + * Defines: */ -#define FALSE 0 -#define TRUE 1 - -#define Y_LOG_FN "yait.log" /* log file read */ -#define Y_OPS_FN "yait.ops" /* frame operation file written */ +#define Y_LOG_FN "yait.log" /* log file read */ +#define Y_OPS_FN "yait.ops" /* frame operation file written */ /* frame ops */ -#define Y_OP_ODD 0x10 -#define Y_OP_EVEN 0x20 -#define Y_OP_PAT 0x30 - -#define Y_OP_NOP 0x0 -#define Y_OP_SAVE 0x1 -#define Y_OP_COPY 0x2 -#define Y_OP_DROP 0x4 -#define Y_OP_DEINT 0x8 - - -/* - * Globals: - */ - -FILE *Log_fp; /* output log file */ -FILE *Ops_fp; /* input frame ops file */ - -uint8_t *Fbuf; /* video frame buffer */ -int Codec; /* internal codec */ -int Fn; /* frame number */ - - -/* - * Prototypes: - */ - -static int yait_get_config( char* ); -static int yait_init( char* ); -static int yait_fini( void ); -static int yait_process( vframe_list_t* ); - -static void yait_compare( vframe_list_t*, uint8_t*, int ); -static void yait_cmp_rgb( uint8_t*, uint8_t*, int, int, int*, int* ); -static void yait_cmp_yuv( uint8_t*, uint8_t*, int, int, int*, int* ); -static int yait_ops( vframe_list_t* ); -static int yait_ops_chk( void ); -static int yait_ops_get( char*, int, int* ); -static int yait_ops_decode( char*, int* ); -static void yait_put_rows( uint8_t*, uint8_t*, int, int, int ); - - -/* - * tc_filter: - * YAIT filter main entry point. Single instance. - */ - -int -tc_filter( frame_list_t *ptr_, char *opt ) - { - vframe_list_t *ptr = (vframe_list_t*) ptr_; - - if( ptr->tag & TC_AUDIO ) - return( 0 ); - - if( ptr->tag & TC_FILTER_GET_CONFIG ) - return( yait_get_config(opt) ); - - if( ptr->tag & TC_FILTER_INIT ) - return( yait_init(opt) ); - - if( ptr->tag & TC_FILTER_CLOSE ) - return( yait_fini() ); - - if( ptr->tag & TC_PRE_S_PROCESS ) - return( yait_process(ptr) ); - - return( 0 ); - } - - -/* - * yait_get_config: - */ - -static int -yait_get_config( char *opt ) - { - optstr_filter_desc( opt, MOD_NAME, MOD_CAP, MOD_VERSION, MOD_AUTHOR, "VRYE", "1" ); - optstr_param( opt, "log", "Compute and write yait delta log file", "%s", "" ); - optstr_param( opt, "ops", "Read and apply yait frame operation file", "%s", "" ); - - return( 0 ); - } - - -/* - * yait_init: - */ - -static int -yait_init( char *opt ) - { - static vob_t *vob; - char buf[256], *fn; - const char *p; - int n; - - if( verbose ) - { - tc_log_info( MOD_NAME, "%s %s", MOD_VERSION, MOD_CAP ); - tc_log_info( MOD_NAME, "options=%s", opt ); - } - - vob = tc_get_vob(); - if( !vob ) - { - tc_log_error( MOD_NAME, "cannot get VOB info." ); - return( -1 ); - } - - Codec = vob->im_v_codec; - fn = NULL; - - /* log file */ - p = optstr_lookup( opt, "log" ); - if( p ) - { - fn = Y_LOG_FN; - n = optstr_get( opt, "log", "%[^:]", buf ); - if( n > 0 ) - fn = buf; - - Log_fp = fopen( fn, "w" ); - if( !Log_fp ) - { - perror( "fopen" ); - tc_log_error( MOD_NAME, "cannot create log file, '%s'", buf ); - return( -1 ); - } - } - - /* ops file */ - p = optstr_lookup( opt, "ops" ); - if( p ) - { - fn = Y_OPS_FN; - n = optstr_get( opt, "ops", "%[^:]", buf ); - if( n > 0 ) - fn = buf; - - Ops_fp = fopen( fn, "r" ); - if( !Ops_fp ) - { - perror( "fopen" ); - tc_log_error( MOD_NAME, "cannot open yait ops file, '%s'", buf ); - return( -1 ); - } - - if( !yait_ops_chk() ) - { - tc_log_error( MOD_NAME, "invalid yait ops file" ); - return( -1 ); - } - } - - if( !Log_fp && !Ops_fp ) - { - tc_log_error( MOD_NAME, "at least one operation (log|ops) must be specified" ); - return( -1 ); - } - - if( Log_fp && Ops_fp ) - { - tc_log_error( MOD_NAME, "only one operation (log|ops) may be specified" ); - return( -1 ); - } - - if( Log_fp ) - { - tc_log_info( MOD_NAME, "Generating YAIT delta log file '%s'", fn ); - tc_log_info( MOD_NAME, "Forcing --hard_fps, -f 30,4, --export_fps 30,4" ); - - /* try to lock everything in at 30 fps */ - vob->hard_fps_flag = TC_TRUE; - vob->im_frc = 4; - vob->ex_frc = 4; - vob->fps = NTSC_VIDEO; - vob->ex_fps = NTSC_VIDEO; - } - - if( Ops_fp ) - { - tc_log_info( MOD_NAME, "Applying YAIT frame operations file '%s'", fn ); - tc_log_info( MOD_NAME, "Forcing --hard_fps, -f 30,4, --export_fps 24,1" ); - - /* try to lock import at 30 fps, export at 24 fps */ - vob->hard_fps_flag = TC_TRUE; - vob->im_frc = 4; - vob->ex_frc = 1; - vob->fps = NTSC_VIDEO; - vob->ex_fps = NTSC_FILM; - } - - Fbuf = tc_malloc( SIZE_RGB_FRAME ); - if( !Fbuf ) - { - perror( "tc_malloc" ); - tc_log_error( MOD_NAME, "cannot allocate frame buffer" ); - return( -1 ); - } - - memset( Fbuf, 0, SIZE_RGB_FRAME ); - - Fn = -1; - - return( 0 ); - } - - -/* - * yait_fini: - */ - -static int -yait_fini( void ) - { - if( Log_fp ) - fclose( Log_fp ); - if( Ops_fp ) - fclose( Ops_fp ); - if( Fbuf ) - free( Fbuf ); - - Log_fp = NULL; - Ops_fp = NULL; - Fbuf = NULL; - - return( 0 ); - } - - -/* - * yait_process: - */ - -static int -yait_process( vframe_list_t *ptr ) - { - if( Fn == -1 ) - { - Fn = ptr->id; - ac_memcpy( Fbuf, ptr->video_buf, ptr->video_size ); - } - - if( ptr->id != Fn ) - { - tc_log_error( MOD_NAME, "inconsistent frame numbers" ); - yait_fini(); - return( -1 ); - } - - if( Log_fp ) - { - yait_compare( ptr, Fbuf, Fn ); - ac_memcpy( Fbuf, ptr->video_buf, ptr->video_size ); - } - - if( Ops_fp ) - if( !yait_ops(ptr) ) - { - yait_fini(); - return( -1 ); - } - - Fn++; - return( 0 ); - } - - -/* - * yait_compare: - */ - -static void -yait_compare( vframe_list_t *ptr, uint8_t *lv, int fn ) - { - uint8_t *cv; - int ed, od; - int w, h; - - cv = ptr->video_buf; - w = ptr->v_width; - h = ptr->v_height; - - if( Codec == CODEC_RGB ) - yait_cmp_rgb( lv, cv, w, h, &ed, &od ); - else - yait_cmp_yuv( lv, cv, w, h, &ed, &od ); - - fprintf( Log_fp, "%d: e: %d, o: %d\n", fn, ed, od ); - - /* BUG: until the blocked tcdecode pipe problem is fixed... */ - if( !(fn%5) ) - fflush( Log_fp ); - } - - -/* - * yait_cmp_rgb: - */ - -static void -yait_cmp_rgb( uint8_t *lv, uint8_t *cv, int w, int h, int *ed, int *od ) - { - uint8_t *lp, *cp; - int x, y, p; - int e, o; - - /* even row delta */ - e = 0; - for( y=0; y<h; y+=2 ) - { - p = y * w * 3; - lp = lv + p; - cp = cv + p; - for( x=0; x<w; x++ ) - { - e += abs( *lp++ - *cp++ ); - e += abs( *lp++ - *cp++ ); - e += abs( *lp++ - *cp++ ); - } - } - - /* odd row delta */ - o = 0; - for( y=1; y<h; y+=2 ) - { - p = y * w * 3; - lp = lv + p; - cp = cv + p; - for( x=0; x<w; x++ ) - { - o += abs( *lp++ - *cp++ ); - o += abs( *lp++ - *cp++ ); - o += abs( *lp++ - *cp++ ); - } - } - - *ed = e; - *od = o; - } - - -/* - * yait_cmp_yuv: - */ - -static void -yait_cmp_yuv( uint8_t *lv, uint8_t *cv, int w, int h, int *ed, int *od ) - { - uint8_t *lp, *cp; - int x, y, p; - int e, o; - - /* even row delta */ - e = 0; - for( y=0; y<h; y+=2 ) - { - /* y */ - p = y * w; - lp = lv + p; - cp = cv + p; - for( x=0; x<w; x++ ) - e += abs( *lp++ - *cp++ ); - - /* uv */ - p = w*h + y * w/2; - lp = lv + p; - cp = cv + p; - for( x=0; x<w/2; x++ ) - e += abs( *lp++ - *cp++ ); - } - - /* odd row delta */ - o = 0; - for( y=1; y<h; y+=2 ) - { - /* y */ - p = y * w; - lp = lv + p; - cp = cv + p; - for( x=0; x<w; x++ ) - o += abs( *lp++ - *cp++ ); - - /* uv */ - p = w*h + y * w/2; - lp = lv + p; - cp = cv + p; - for( x=0; x<w/2; x++ ) - o += abs( *lp++ - *cp++ ); - } - - *ed = e; - *od = o; - } - - -/* - * yait_ops: - */ - -static int -yait_ops( vframe_list_t *ptr ) - { - char buf[256]; - uint8_t *v; - int mode, op; - int w, h; - - v = ptr->video_buf; - w = ptr->v_width; - h = ptr->v_height; - - fgets( buf, 256, Ops_fp ); - op = yait_ops_get( buf, Fn, &mode ); - - if( op < 0 ) - return( FALSE ); - - if( op & Y_OP_SAVE ) - yait_put_rows( Fbuf, v, w, h, op&Y_OP_PAT ); - - if( op & Y_OP_COPY ) - yait_put_rows( v, Fbuf, w, h, op&Y_OP_PAT ); - - if( op & Y_OP_DROP ) - ptr->attributes |= TC_FRAME_IS_SKIPPED; - - if( op & Y_OP_DEINT ) - { - ptr->attributes |= TC_FRAME_IS_INTERLACED; - ptr->deinter_flag = mode; - } - - return( TRUE ); - } - - -/* - * yait_ops_chk: - */ - -static int -yait_ops_chk( void ) - { - char buf[256], *p; - int fn, op; - - fscanf( Ops_fp, "%d:", &fn ); - rewind( Ops_fp ); - for( ;; ) - { - p = fgets( buf, 256, Ops_fp ); - if( !p ) - break; - - op = yait_ops_get( buf, fn, NULL ); - if( op < 0 ) - return( FALSE ); - fn++; - } - - rewind( Ops_fp ); - return( TRUE ); - } - - -/* - * yait_ops_get: - */ - -static int -yait_ops_get( char *buf, int fn, int *mode ) - { - char str[256]; - int op, f, n; - - f = -1; - str[0] = 0; - - n = sscanf( buf, "%d: %s\n", &f, str ); - if( n < 1 ) - { - if( feof(Ops_fp) ) - tc_log_error( MOD_NAME, "truncated yait ops file, frame: %d", fn ); - else - tc_log_error( MOD_NAME, "invalid yait ops format, frame: %d", fn ); - return( -1 ); - } - - if( f != fn ) - { - tc_log_error( MOD_NAME, "invalid yait ops frame number, frame: %d", fn ); - return( -1 ); - } - - op = yait_ops_decode( str, mode ); - if( op < 0 ) - { - tc_log_error( MOD_NAME, "invalid yait ops code, frame: %d", fn ); - return( -1 ); - } - - return( op ); - } - - -/* - * yait_ops_decode: - */ - -static int -yait_ops_decode( char *str, int *mode ) - { - int op, c; - - op = 0; - while( *str ) - { - c = *str++; - if( c>='1' && c<='5' ) - { - op |= Y_OP_DEINT; - if( mode ) - *mode = c - '0'; - continue; - } - - switch( c ) - { - case 'o': - op |= Y_OP_ODD; - break; - case 'e': - op |= Y_OP_EVEN; - break; - case 's': - op |= Y_OP_SAVE; - break; - case 'c': - op |= Y_OP_COPY; - break; - case 'd': - op |= Y_OP_DROP; - break; - default: - return( -1 ); - break; - } - } - - return( op ); - } - - -/* - * yait_put_rows: - */ - -static void -yait_put_rows( uint8_t *dst, uint8_t *src, int w, int h, int flg ) - { - int y, o; - - y = (flg==Y_OP_EVEN) ? 0 : 1; - - if( Codec == CODEC_RGB ) - { - for( ; y<h; y+=2 ) - { - o = y * w * 3; - ac_memcpy( dst+o, src+o, w*3 ); - } - } - else - { - for( ; y<h; y+=2 ) - { - /* y (luminance) */ - o = y * w; - ac_memcpy( dst+o, src+o, w ); - - /* 2 * h/2 blocks (u and v) = h */ - o = w*h + y * w/2; - ac_memcpy( dst+o, src+o, w/2 ); - } - } - } +enum { + Y_OP_ODD = 0x10, + Y_OP_EVEN = 0x20, + Y_OP_PAT = 0x30, +}; + +enum { + Y_OP_NOP = 0x0, + Y_OP_SAVE = 0x1, + Y_OP_COPY = 0x2, + Y_OP_DROP = 0x4, + Y_OP_DEINT = 0x8, +}; + + +typedef void (*yait_cmp_hook_fn)(const uint8_t *lv, const uint8_t *cv, + int w, int h, int *ed , int *od); + + +/* + * Globals: + */ + +FILE *Log_fp = NULL; /* output log file */ +FILE *Ops_fp = NULL; /* input frame ops file */ + +uint8_t *Fbuf = NULL; /* video frame buffer */ +int Codec; /* internal codec */ +int Frame_num; /* frame number */ +yait_cmp_hook_fn cmp_fn = NULL; /* real compare function (saves one if()) */ + + +/* + * Prototypes: + */ + +static int yait_get_config(char* opt); +static int yait_init(const char* opt); +static int yait_fini(void); +static int yait_process(vframe_list_t* ptr); + +static void yait_compare(vframe_list_t* ptr, const uint8_t* lv, int fn); +static void yait_cmp_rgb(const uint8_t *lv, const uint8_t *cv, + int w, int h, int *ed, int *od); +static void yait_cmp_yuv(const uint8_t *lv, const uint8_t *cv, + int w, int h, int *ed, int *od); +static int yait_ops(vframe_list_t* ptr); +static int yait_ops_chk(void); +static int yait_ops_get(const char* buf, int fn, int* mode); +static int yait_ops_decode(const char *str, int *mode); +static void yait_put_rows(uint8_t *dst, const uint8_t *src, + int w, int h, int flag); + + +/* + * tc_filter: + * YAIT filter main entry point. Single instance. + */ +int tc_filter(frame_list_t *ptr_, char *opt) +{ + vframe_list_t *ptr = (vframe_list_t*) ptr_; + + if (ptr->tag & TC_AUDIO) + return TC_ERROR; + + if (ptr->tag & TC_FILTER_GET_CONFIG) + return yait_get_config(opt); + + if (ptr->tag & TC_FILTER_INIT) + return yait_init(opt); + + if (ptr->tag & TC_FILTER_CLOSE) + return yait_fini(); + + if (ptr->tag & TC_PRE_S_PROCESS) + return yait_process(ptr); + + return TC_ERROR; +} + + +/* + * yait_get_config: + */ +static int yait_get_config(char *opt) +{ + optstr_filter_desc(opt, MOD_NAME, MOD_CAP, MOD_VERSION, + MOD_AUTHOR, "VRYE", "1"); + optstr_param(opt, "log", "Compute and write yait delta log file", + "%s", ""); + optstr_param(opt, "ops", "Read and apply yait frame operation file", + "%s", ""); + + return TC_OK; +} + + +/* + * yait_init: + */ +static int yait_init(const char *opt) +{ + vob_t *vob = tc_get_vob(); + char buf[256], *fn = NULL; + const char *p = NULL; + int n; + + if (verbose) { + tc_log_info( MOD_NAME, "%s %s", MOD_VERSION, MOD_CAP ); + tc_log_info( MOD_NAME, "options=%s", opt ); + } + + /* FIXME: options=help handling still missing */ + + if (!vob) { + /* can't happen */ + tc_log_error(MOD_NAME, "cannot get VOB info."); + return TC_ERROR; + } + + Frame_num = -1; + Codec = vob->im_v_codec; + if (Codec == CODEC_RGB) + cmp_fn = yait_cmp_rgb; + else + cmp_fn = yait_cmp_yuv; + + /* log file */ + p = optstr_lookup(opt, "log"); + if (p != NULL) { + fn = Y_LOG_FN; + n = optstr_get(p, "log", "%[^:]", buf); // XXX + if (n > 0) + fn = buf; + + Log_fp = fopen(fn, "w"); + if (!Log_fp) { + tc_log_error(MOD_NAME, "cannot create log file, '%s'", buf); + return TC_ERROR; + } + } + + /* ops file */ + p = optstr_lookup(opt, "ops"); + if (p != NULL) { + fn = Y_OPS_FN; + n = optstr_get(p, "ops", "%[^:]", buf); // XXX + if (n > 0) + fn = buf; + + Ops_fp = fopen(fn, "r"); + if(!Ops_fp) { + tc_log_error(MOD_NAME, + "cannot open yait ops file, '%s'", buf); + return TC_ERROR; + } + + if (!yait_ops_chk()) { + tc_log_error(MOD_NAME, "invalid yait ops file"); + return TC_ERROR; + } + } + + if (!Log_fp && !Ops_fp) { + tc_log_error(MOD_NAME, + "at least one operation (log|ops) must be specified"); + return TC_ERROR; + } + + if (Log_fp && Ops_fp) { + tc_log_error(MOD_NAME, + "only one operation (log|ops) may be specified"); + return TC_ERROR; + } + + /* try to lock import at 30 fps (needed by both modes ) */ + vob->hard_fps_flag = TC_TRUE; + vob->im_frc = 4; + vob->fps = NTSC_VIDEO; + + if (Log_fp) { + tc_log_info(MOD_NAME, "Generating YAIT delta log file '%s'", fn); + tc_log_info(MOD_NAME, + "Forcing --hard_fps, -f 30,4, --export_fps 30,4"); + + /* try to lock export too at 30 fps */ + vob->ex_fps = NTSC_VIDEO; + vob->ex_frc = 4; + } + + if (Ops_fp) { + tc_log_info(MOD_NAME, + "Applying YAIT frame operations file '%s'", fn); + tc_log_info(MOD_NAME, + "Forcing --hard_fps, -f 30,4, --export_fps 24,1"); + + /* try to lock export at 24 fps */ + vob->ex_fps = NTSC_FILM; + vob->ex_frc = 1; + } + + Fbuf = tc_zalloc(SIZE_RGB_FRAME); + if (!Fbuf) { + tc_log_error(MOD_NAME, "cannot allocate frame buffer"); + return TC_ERROR; + } + return TC_OK; +} + + +/* + * yait_fini: + */ +static int yait_fini(void) +{ + if (Log_fp != NULL) { + fclose(Log_fp); + Log_fp = NULL; + } + if (Ops_fp != NULL) { + fclose(Ops_fp); + Ops_fp = NULL; + } + if (Fbuf != NULL) { + tc_free(Fbuf); + Fbuf = NULL; + } + + return TC_OK; +} + + +/* + * yait_process: + */ +static int yait_process(vframe_list_t *ptr) +{ + if (Frame_num == -1) { + /* first run */ + Frame_num = ptr->id; + ac_memcpy(Fbuf, ptr->video_buf, ptr->video_size); + } + + if (ptr->id != Frame_num) { + tc_log_error( MOD_NAME, "inconsistent frame numbers" ); + yait_fini(); + return TC_ERROR; + } + + if (Log_fp != NULL) { + yait_compare(ptr, Fbuf, Frame_num); + ac_memcpy(Fbuf, ptr->video_buf, ptr->video_size); + } + + if (Ops_fp != NULL) { + if (!yait_ops(ptr)) { + yait_fini(); + return TC_ERROR; + } + } + + Frame_num++; + return TC_OK; +} + + +/* + * yait_compare: + */ +static void yait_compare(vframe_list_t *ptr, const uint8_t *lv, int fn) +{ + const uint8_t *cv = ptr->video_buf; // XXX: checking? + int w = ptr->v_width, h = ptr->v_height; // XXX: checking? + int ed, od; + + cmp_fn(lv, cv, w, h, &ed, &od); + fprintf(Log_fp, "%d: e: %d, o: %d\n", fn, ed, od); // XXX: write check? + + /* FIXME BUG: until the blocked tcdecode pipe problem is fixed... */ + if (!(fn % 5)) + fflush(Log_fp); // XXX: write check? +} + + +/* + * yait_cmp_rgb: + */ +static void yait_cmp_rgb(const uint8_t *lv, const uint8_t *cv, + int w, int h, int *ed, int *od) +{ + const uint8_t *lp = NULL, *cp = NULL; + int x, y, p; + int e, o; /* we really need those two? */ + + /* even row delta */ + e = 0; + for (y = 0; y < h; y += 2) { + p = y * w * 3; + lp = lv + p; + cp = cv + p; + for (x = 0; x < w; x++) { + e += abs(*lp++ - *cp++); + e += abs(*lp++ - *cp++); + e += abs(*lp++ - *cp++); + } + } + + /* odd row delta */ + o = 0; + for (y = 1; y < h; y += 2) { + p = y * w * 3; + lp = lv + p; + cp = cv + p; + for (x = 0; x < w; x++) { + o += abs(*lp++ - *cp++); + o += abs(*lp++ - *cp++); + o += abs(*lp++ - *cp++); + } + } + + *ed = e; + *od = o; +} + + +/* + * yait_mp_yuv: + */ +static void yait_cmp_yuv(const uint8_t *lv, const uint8_t *cv, + int w, int h, int *ed, int *od) +{ + const uint8_t *lp = NULL, *cp = NULL; + int x, y, p; + int e, o; /* we really need those two? */ + + /* even row delta */ + e = 0; + for (y = 0; y < h; y += 2) { + /* y */ + p = y * w; + lp = lv + p; + cp = cv + p; + for (x = 0; x < w; x++) + e += abs(*lp++ - *cp++); + + /* uv */ + p = w * h + y * w/2; + lp = lv + p; + cp = cv + p; + for (x = 0; x < w/2; x++) + e += abs(*lp++ - *cp++); + } + + /* odd row delta */ + o = 0; + for (y = 1; y < h; y += 2) { + /* y */ + p = y * w; + lp = lv + p; + cp = cv + p; + for (x = 0; x < w; x++) + o += abs(*lp++ - *cp++); + + /* uv */ + p = w * h + y * w/2; + lp = lv + p; + cp = cv + p; + for(x = 0; x < w/2; x++) + o += abs(*lp++ - *cp++); + } + + *ed = e; + *od = o; +} + + +/* + * yait_ops: + */ +static int yait_ops(vframe_list_t *ptr) +{ + char buf[TC_BUF_LINE]; + int mode, op; + uint8_t *v = ptr->video_buf; + int w = ptr->v_width, h = ptr->v_height; + + fgets(buf, TC_BUF_LINE, Ops_fp); + op = yait_ops_get(buf, Frame_num, &mode); + + if (op < 0) + return TC_FALSE; + + if (op & Y_OP_SAVE) + yait_put_rows(Fbuf, v, w, h, op & Y_OP_PAT); + + if (op & Y_OP_COPY) + yait_put_rows(v, Fbuf, w, h, op & Y_OP_PAT); + + if (op & Y_OP_DROP) + ptr->attributes |= TC_FRAME_IS_SKIPPED; + + if (op & Y_OP_DEINT) { + ptr->attributes |= TC_FRAME_IS_INTERLACED; + ptr->deinter_flag = mode; + } + + return TC_TRUE; +} + + +/* + * yait_ops_chk: + */ +static int yait_ops_chk(void) +{ + char buf[TC_BUF_LINE], *p = NULL; + int fn, op; + + fscanf(Ops_fp, "%d:", &fn); + rewind(Ops_fp); + for (;;) { + p = fgets(buf, TC_BUF_LINE, Ops_fp); + if (!p) + break; + + op = yait_ops_get(buf, fn, NULL); + if (op < 0) + return TC_FALSE; + fn++; + } + + rewind(Ops_fp); + return TC_TRUE; +} + + +/* + * yait_ops_get: + */ +static int yait_ops_get(const char *buf, int fn, int *mode) +{ + char str[TC_BUF_LINE]; + int op, f = -1, n; + + str[0] = 0; // XXX + + n = sscanf(buf, "%d: %s\n", &f, str); + if (n < 1) { + if (feof(Ops_fp)) + tc_log_error(MOD_NAME, "truncated yait ops file, frame: %d", fn); + else + tc_log_error(MOD_NAME, "invalid yait ops format, frame: %d", fn); + return -1; + } + + if (f != fn) { + tc_log_error(MOD_NAME, + "invalid yait ops frame number, frame: %d", fn); + return -1; + } + + op = yait_ops_decode(str, mode); + if (op < 0) { + tc_log_error(MOD_NAME, "invalid yait ops code, frame: %d", fn); + return -1; + } + + return op; +} + + +/* + * yait_ops_decode: + */ +static int yait_ops_decode(const char *str, int *mode) +{ + int op = 0, c; + + while (*str) { + c = *str++; + if (c >= '1' && c <= '5') { + op |= Y_OP_DEINT; + if (mode) + *mode = c - '0'; + continue; + } + + switch (c) { + case 'o': + op |= Y_OP_ODD; + break; + case 'e': + op |= Y_OP_EVEN; + break; + case 's': + op |= Y_OP_SAVE; + break; + case 'c': + op |= Y_OP_COPY; + break; + case 'd': + op |= Y_OP_DROP; + break; + default: + return -1; + } + } + + return op; +} + + +/* + * yait_put_rows: + */ +static void yait_put_rows(uint8_t *dst, const uint8_t *src, int w, int h, int flag) +{ + int y = ((flag == Y_OP_EVEN) ?0 :1), o; + + if (Codec == CODEC_RGB) { + for(; y < h; y += 2) { + o = y * w * 3; + ac_memcpy(dst + o, src + o, w * 3); + } + } else { + for(; y < h; y += 2) { + /* y (luminance) */ + o = y * w; + ac_memcpy(dst + o, src + o, w); + + /* 2 * h/2 blocks (u and v) = h */ + o = w * h + y * w/2; + ac_memcpy(dst + o, src + o, w/2); + } + } +} + + +/*************************************************************************/ + +/* + * Local variables: + * c-file-style: "stroustrup" + * c-file-offsets: ((case-label . *) (statement-case-intro . *)) + * indent-tabs-mode: nil + * End: + * + * vim: expandtab shiftwidth=4: + */