You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1970 lines
37 KiB

/*
* tcyait.c
*
* Copyright (C) Allan Snider - February 2007
*
* This file is part of transcode, a video stream processing tool
*
* transcode is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* transcode is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GNU Make; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
/*
* tcyait:
* Yet Another Inverse Telecine filter.
*
* Usage:
* tcyait [-dnlotbwmEO] [arg...]
* -d Print debug information to stdout
* -n Do not drop frames, always de-interlace
* -k No forced keep frames
* -l log Input yait log file name
* -o ops Output yait frame ops file name
* -t thresh Interlace detection threshold (>1)
* -b blend forced blend threshold (>thresh)
* -w size Drop frame look ahead window (0-20)
* -m mode Transcode blend method (0-5)
* -E thresh Even pattern threhold [thresh]
* -O thresh Odd pattern threhold [thresh]
* -N noise minimum normalized delta, else considered noise
*
* By default, reads "yait.log" and produces "yait.ops".
*
* Description:
*
* Read a yait log file (generated via -J yait=log), and analyze it to
* produce a yait frame operations file. The frame operations file contains
* commands to the yait filter to drop, copy or save rows (to de-interlace),
* or blend frames. This will convert from NTSC 29.97 to 23.976 fps. The file
* generated is used as input for another transcode pass (-J yait=ops).
*/
#define YAIT_VERSION "v0.2"
#define TRUE 1
#define FALSE 0
/* program defaults */
#define Y_LOG_FN "yait.log" /* log file read */
#define Y_OPS_FN "yait.ops" /* frame operation file written */
#define Y_THRESH 1.2 /* even/odd ratio to detect interlace */
#define Y_DROPWIN_SIZE 15 /* drop frame look ahead window */
#define Y_DEINT_MODE 3 /* default transcode de-interlace mode */
/* limits */
#define Y_DEINT_MIN 0
#define Y_DEINT_MAX 5
#define Y_DROPWIN_MIN 0
#define Y_DROPWIN_MAX 60
#define Y_MTHRESH 1.02 /* minimum ratio allowing de-interlace */
#define Y_NOISE 0.003 /* normalized delta too weak, noise */
#define Y_SCENE_CHANGE 0.15 /* normalized delta > 15% of max delta */
/* force de-interlace */
#define Y_FBLEND 1.6 /* if ratio is > this */
#define Y_FWEIGHT 0.01 /* if over this weight */
/* frame operation flags */
#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
/* group flags */
#define Y_HAS_DROP 1
#define Y_BANK_DROP 2
#define Y_WITHDRAW_DROP 3
#define Y_BORROW_DROP 4
#define Y_RETURN_DROP 5
#define Y_FORCE_DEINT 6
#define Y_FORCE_DROP 7
#define Y_FORCE_KEEP 8
/* frame information */
typedef struct fi_t Fi;
struct fi_t
{
double r; /* even/odd delta ratio, filtered */
double ro; /* ratio, original value */
double w; /* statistical strength */
double nd; /* normalized delta */
int fn; /* frame number */
int ed; /* even row delta */
int od; /* odd row delta */
int gi; /* group array index */
int ip; /* telecine pattern */
int op; /* frame operation, nop, save/copy row */
int drop; /* frame is to be dropped */
int gf; /* group flag */
Fi *next;
};
/*
* globals:
*/
char *Prog; /* argv[0] */
char *LogFn; /* log file name, default "yait.log" */
char *OpsFn; /* ops file name, default "yait.ops" */
double Thresh; /* row delta ratio interlace detection threshold */
double EThresh; /* even interlace detection threshold */
double OThresh; /* odd interlace detection threshold */
double Blend; /* force frame blending over this threshold */
double Noise; /* minimum normalized delta */
int DropWin; /* drop frame look ahead window */
int DeintMode; /* transcode de-interlace mode, (1-5) */
int DebugFi; /* dump debug frame info */
int NoDrops; /* force de-interlace everywhere (non vfr) */
int NoKeeps; /* Don't force keep frames (allows a/v sync drift) */
FILE *LogFp; /* log file */
FILE *OpsFp; /* ops file */
Fi *Fl; /* frame list */
Fi **Fa; /* frame array */
Fi **Ga; /* group array */
int *Da; /* drop count array */
int Nf; /* number of frames */
int Ng; /* number of elements in Ga */
int Nd; /* number of elements in Da */
int Md; /* max delta */
int Stat_nd; /* total number of dropped frames */
int Stat_nb; /* total number of blended frames */
int Stat_no; /* total number of odd interlaced pairs */
int Stat_ne; /* total number of even interlaced pairs */
int Stat_fd; /* total number of forced drops */
int Stat_fk; /* total number of forced keeps */
/*
* protos:
*/
static void yait_parse_args( int, char** );
static void yait_chkac( int* );
static void yait_usage( void );
static void yait_read_log( void );
static double yait_calc_ratio( int, int );
static void yait_find_ip( void );
static void yait_chk_ip( int );
static void yait_chk_pairs( int );
static void yait_chk_tuplets( int );
static int yait_find_odd( double, int, double*, int );
static int yait_find_even( double, int, double*, int );
static int yait_ffmin( int, int );
static int yait_ffmax( int, int );
static int yait_m5( int );
static void yait_mark_grp( int, int, double );
static void yait_find_drops( void );
static int yait_cnt_drops( int );
static int yait_extra_drop( int );
static int yait_missing_drop( int );
static void yait_keep_frame( int );
static int yait_get_hdrop( int, int* );
static void yait_ivtc_keep( int );
static void yait_ivtc_grps( void );
static int yait_scan_bk( int );
static int yait_scan_fw( int );
static void yait_drop_frame( int );
static int yait_ivtc_grp( int, int, int );
static double yait_tst_ip( int, int );
static void yait_deint( void );
static void yait_write_ops( void );
static char *yait_write_op( Fi* );
static void yait_fini( void );
static void yait_debug_fi( void );
static char *yait_op( int op );
static char *yait_drop( Fi *f );
static char *yait_grp( int flg );
/*
* main:
*/
int
main( int argc, char **argv )
{
/* parse args */
yait_parse_args( argc, argv );
LogFp = fopen( LogFn, "r" );
if( !LogFp )
{
perror( "fopen" );
fprintf( stderr, "Cannot open YAIT delta log file (%s)\n", LogFn );
exit( 1 );
}
OpsFp = fopen( OpsFn, "w" );
if( !OpsFp )
{
perror( "fopen" );
fprintf( stderr, "Cannot create YAIT frame ops file (%s)\n", OpsFn );
exit( 1 );
}
/* read the log */
yait_read_log();
/* find interleave patterns */
yait_find_ip();
/* find drop frames */
yait_find_drops();
/* complete groups missing an interleave pattern */
yait_ivtc_grps();
/* let transcode de-interlace frames we missed */
yait_deint();
/* print frame ops file */
yait_write_ops();
if( DebugFi )
yait_debug_fi();
/* graceful exit */
yait_fini();
return( 0 );
}
/*
* yait_parse_args:
*/
static void
yait_parse_args( int argc, char **argv )
{
int opt;
char *p;
LogFn = Y_LOG_FN;
OpsFn = Y_OPS_FN;
Thresh = Y_THRESH;
EThresh = 0;
OThresh = 0;
Blend = Y_FBLEND;
Noise = Y_NOISE;
DeintMode = Y_DEINT_MODE;
DropWin = Y_DROPWIN_SIZE;
--argc;
Prog = *argv++;
while( (p = *argv) )
{
if( *p++ != '-' )
break;
while( (opt = *p++) )
switch( opt )
{
case 'd':
DebugFi = TRUE;
break;
case 'n':
NoDrops = TRUE;
break;
case 'k':
NoKeeps = TRUE;
break;
case 'l':
yait_chkac( &argc );
LogFn = *++argv;
break;
case 'o':
yait_chkac( &argc );
OpsFn = *++argv;
break;
case 't':
yait_chkac( &argc );
Thresh = atof( *++argv );
break;
case 'E':
yait_chkac( &argc );
EThresh = atof( *++argv );
break;
case 'O':
yait_chkac( &argc );
OThresh = atof( *++argv );
break;
case 'b':
yait_chkac( &argc );
Blend = atof( *++argv );
break;
case 'N':
yait_chkac( &argc );
Noise = atof( *++argv );
break;
case 'w':
yait_chkac( &argc );
DropWin = atoi( *++argv );
break;
case 'm':
yait_chkac( &argc );
DeintMode = atoi( *++argv );
break;
default:
yait_usage();
break;
}
--argc;
argv++;
}
if( Thresh <= 1 )
{
printf( "Invalid threshold specified (%g).\n\n", Thresh );
yait_usage();
}
if( Blend <= Thresh )
{
printf( "Invalid blend threshold specified (%g).\n\n", Blend );
yait_usage();
}
if( DropWin<Y_DROPWIN_MIN || DropWin>Y_DROPWIN_MAX )
{
printf( "Invalid drop window size specified (%d).\n\n", DropWin );
yait_usage();
}
if( DeintMode<Y_DEINT_MIN || DeintMode>Y_DEINT_MAX )
{
printf( "Invalid de-interlace method specified (%d).\n\n", DeintMode );
yait_usage();
}
if( !EThresh )
EThresh = Thresh;
if( !OThresh )
OThresh = Thresh;
if( argc )
yait_usage();
}
/*
* yait_chkac:
*/
static void
yait_chkac( int *ac )
{
if( *ac < 1 )
yait_usage();
--*ac;
}
/*
* yait_usage:
*/
static void
yait_usage( void )
{
printf( "Usage: %s [-dnklotbwmEON] [arg...] \n", Prog );
printf( "\t-d\t\tPrint debug information to stdout.\n" );
printf( "\t-n\t\tDo not drop frames, always de-interlace.\n" );
printf( "\t-k\t\tNo forced keep frames.\n" );
printf( "\t-l log\t\tInput yait log file name [%s].\n", Y_LOG_FN );
printf( "\t-o ops\t\tOutput yait frame ops file name [%s].\n", Y_OPS_FN );
printf( "\t-t thresh\tInterlace detection threshold (>1) [%g].\n", Y_THRESH );
printf( "\t-b blend\tforced blend threshold (>thresh) [%g].\n", Y_FBLEND );
printf( "\t-w size\t\tDrop frame look ahead window (0-20) [%d].\n", Y_DROPWIN_SIZE );
printf( "\t-m mode\t\tTranscode blend method (0-5) [%d].\n", Y_DEINT_MODE );
printf( "\t-E thresh\tEven pattern threshold [%g].\n", Y_THRESH );
printf( "\t-O thresh\tOdd pattern threshold [%g].\n", Y_THRESH );
printf( "\t-N noise\tMinimum normalized delta, else noise [%g].\n", Y_NOISE );
printf( "\n" );
exit( 1 );
}
/*
* yait_read_log:
*/
static void
yait_read_log( void )
{
Fi **fa, *pf, *f;
int fn, ed, od;
int s, n;
s = 0;
pf = NULL;
for( Nf=0; ; Nf++ )
{
n = fscanf( LogFp, "%d: e: %d, o: %d\n", &fn, &ed, &od );
if( n != 3 )
break;
/* starting frame number */
if( !Nf )
s = fn;
if( (fn-s) != Nf )
{
printf( "Broken log file, line %d\n", Nf );
exit( 1 );
}
f = (Fi*) malloc( sizeof(Fi) );
if( !f )
{
perror( "malloc" );
exit( 1 );
}
memset( (void*) f, 0, sizeof(Fi) );
if( !Fl )
Fl = f;
if( pf )
pf->next = f;
pf = f;
f->r = yait_calc_ratio( ed, od );
f->ro = f->r;
f->fn = fn;
f->ed = ed;
f->od = od;
f->ip = -1;
}
if( !Fl )
{
fprintf( stderr, "Invalid log file.\n" );
exit( 1 );
}
/* number of 5 frame groups */
Nd = Nf / 5;
Fa = (Fi**) malloc( (Nf+1) * sizeof(Fi*) );
Ga = (Fi**) malloc( (Nf+1) * sizeof(Fi*) );
Da = (int*) malloc( Nd * sizeof(int) );
if( !Fa || !Ga || !Da )
{
perror( "malloc" );
exit( 1 );
}
fa = Fa;
for( f=Fl; f; f=f->next )
*fa++ = f;
*fa = NULL;
}
/*
* yait_calc_ratio:
* Compute a ratio between even/odd row deltas. A high ratio indicates an
* interlace present. Use the sign of the ratio to indicate even row (<0), or odd
* row (>0) correlation.
*
* If the magnitude of the ratio is > 1.1, this is usually enough to
* indicate interlacing. A value around 1.0 indicates no row correlation at
* all.
*
* Assigning the ratios in this manner result in the following patterns
* present for interlaced material. Assume 0 for fabs(r)<thresh, else +/- 1:
*
* An odd interlace pattern (for a five frame group) would appear as:
*
* frame: 1 2 3 4 5
* even: a a b c d
* odd: a b c c d
*
* ratio: 0 -1 0 1 0
*
* If we detect this pattern, we assign the following frame operations:
*
* frame: 1 2 3 4 5
* even: a a b c d
* odd: a b c c d
*
* ratio: 0 -1 0 1 0
* op: osd oc
*
* osd = save odd rows and drop the frame
* oc = copy in saved odd rows
*
* This results with:
*
* frame: 1 |2| 3 4 5
* even: a |a| b c d
* odd: a |b|--> b c d
* drop
*
* For even interlace patterns, the signs are reversed, or simply:
*
* ratio: 0 1 0 -1 0
* esd ec
*
* The entire approach of this tool depends on these specific ratio patterns
* to be present, and should be for 2:3 pulldown. Lots of complications arise
* around still and abrupt scene changes. Again, it might be useful for the
* filter to produce a combing co-efficient as well as the delta information.
*
* Side note:
* For yuv, deltas based only on luminance yeilded much stronger
* interlace patterns, however, I suppose there are (rare) cases where
* chroma could be the only indicator, so chroma is included in the
* delta calculation, even though it results in weaker ratios.
*/
static double
yait_calc_ratio( int ed, int od )
{
double r;
r = 1;
/* compute ratio, >1 odd, <-1 even */
if( !ed && !od )
/* duplicate frame */
r = 0;
if( ed && !od )
r = 100;
if( !ed && od )
r = -100;
if( ed && od )
{
r = (double) ed / (double) od;
if( r < 1 )
r = -1.0 / r;
if( r > 100 )
r = 100;
if( r < -100 )
r = -100;
}
return( r );
}
/*
* yait_find_ip:
* - Mark isolated duplicate frames to be hard dropped.
* - Create the group array which is used to processes interleave
* patterns without duplicate frames present.
* - Find the maximum frame delta value. This is used to normalize
* frame deltas to filter out weak frames (noise which may cause
* erroneous interleave patterns to be detected).
* - Detect local interleave patterns.
*/
static void
yait_find_ip( void )
{
Fi *f;
double w;
int m, p, i;
/* mark obvious drop frames */
if( !NoDrops )
for( i=1; i<Nf-1; i++ )
{
f = Fa[i];
if( f->r )
continue;
if( !Fa[i-1]->r && !Fa[i+1]->r )
continue;
f->drop = TRUE;
}
/* create group array, ommiting drops */
Ng = 0;
for( i=0; i<Nf; i++ )
{
f = Fa[i];
if( f->drop )
continue;
f->gi = Ng;
Ga[Ng++] = f;
}
Ga[Ng] = NULL;
/* find max row delta */
m = 0;
for( i=0; i<Nf; i++ )
{
f = Fa[i];
if( f->ed > m )
m = f->ed;
if( f->od > m )
m = f->od;
}
Md = m;
if( !Md )
{
fprintf( stderr, "All empty frames?\n" );
exit( 1 );
}
/* compute normalized row deltas and */
/* filter out weak r values (noise) */
for( i=0; i<Ng; i++ )
{
f = Ga[i];
f->nd = (f->ed + f->od) / (double) Md;
if( f->nd < Noise )
f->r = 1;
}
/* adjust for incomplete interleave patterns */
/* (indexing Fa[0,..,i+6]) */
for( i=0; i<Ng-6; i++ )
yait_chk_ip( i );
/* find interleave patterns */
for( i=0; i<Ng; i++ )
{
f = Ga[i];
if( f->op & Y_OP_COPY )
{
/* finish this group before looking for another pattern */
i++;
continue;
}
p = yait_find_odd( OThresh, i, &w, 4 );
if( p != -1 )
{
yait_mark_grp( p, i, w );
continue;
}
p = yait_find_even( EThresh, i, &w, 4 );
if( p != -1 )
yait_mark_grp( p+10, i, w );
}
}
/*
* yait_chk_ip:
* Two cases to look for. An isolated pair of high r's, and an
* isolated tuplet of high r's. These can be caused by interlacing over
* still and abrupt scene changes.
*/
static void
yait_chk_ip( int n )
{
if( !NoDrops )
yait_chk_pairs( n );
yait_chk_tuplets( n );
}
/*
* yait_chk_pairs:
* Look for patterns of the type:
* i: 0 1 2 3 4 5
* odd: 0 0 -1 1 0 0
* even: 0 0 1 -1 0 0
*
* If detected, force the drop of the (single) interlaced frame.
* De-interlacing would just incur a redundant copy operation.
*/
static void
yait_chk_pairs( int n )
{
Fi *fa[6];
double ra[6];
int i;
for( i=0; i<6; i++ )
{
fa[i] = Ga[n+i];
ra[i] = fabs( fa[i]->r );
}
for( i=2; i<4; i++ )
if( ra[i] < Thresh )
return;
/* adjacent frames to the tuplet must be <thresh */
if( ra[1]>Thresh || ra[4]>Thresh )
return;
/* we only need one edge frame to be <thresh */
if( ra[0]>Thresh && ra[5]>Thresh )
return;
if( fa[2]->r>0 && fa[3]->r>0 )
return;
if( fa[2]->r<0 && fa[3]->r<0 )
return;
/* two isolated high r values of opposite sign */
/* drop the interlaced frame, erase the pattern */
fa[2]->r = 1;
fa[3]->r = 1;
fa[2]->drop = TRUE;
}
/*
* yait_chk_tuplets:
* Look for patterns of the type:
* i: 0 1 2 3 4 5 6
* odd: 0 0 -1 +/-2 1 0 0
* even: 0 0 1 +/-2 -1 0 0
*
* and complete to:
*
* odd: 0 0 -1 0 1 0 0
* even: 0 0 1 0 -1 0 0
*/
static void
yait_chk_tuplets( int n )
{
Fi *fa[7];
double ra[7];
int i;
for( i=0; i<7; i++ )
{
fa[i] = Ga[n+i];
ra[i] = fabs( fa[i]->r );
}
for( i=2; i<5; i++ )
if( ra[i] < Thresh )
return;
/* adjacent frames to the tuplet must be <thresh */
if( ra[1]>Thresh || ra[5]>Thresh )
return;
/* we only need one edge frame to be <thresh */
if( ra[0]>Thresh && ra[6]>Thresh )
return;
if( fa[2]->r>0 && fa[4]->r>0 )
return;
if( fa[2]->r<0 && fa[4]->r<0 )
return;
/* isolated tuplet of high r values of opposite sign */
if( ra[3]>ra[2] || ra[3]>ra[4] )
fa[3]->r = 1;
}
/*
* yait_find_odd:
*/
static int
yait_find_odd( double thresh, int n, double *w, int win )
{
double re, ro;
int me, mo;
int p;
/* find max even/odd correlations */
/* (r<0 - even, r>0 - odd) */
me = yait_ffmin( n, win );
mo = yait_ffmax( n, win );
p = -1;
if( yait_m5(mo-2) == yait_m5(me) )
{
re = fabs( Ga[me]->r );
ro = fabs( Ga[mo]->r );
if( re>thresh && ro>thresh )
{
p = yait_m5( mo - 4 );
if( w )
*w = re + ro;
}
}
return( p );
}
/*
* yait_find_even:
*/
static int
yait_find_even( double thresh, int n, double *w, int win )
{
double re, ro;
int me, mo;
int p;
me = yait_ffmin( n, win );
mo = yait_ffmax( n, win );
p = -1;
if( yait_m5(me-2) == yait_m5(mo) )
{
re = fabs( Ga[me]->r );
ro = fabs( Ga[mo]->r );
if( re>thresh && ro>thresh )
{
p = yait_m5( me - 4 );
if( w )
*w = re + ro;
}
}
return( p );
}
/*
* yait_ffmin:
*/
static int
yait_ffmin( int n, int w )
{
Fi *f;
int m, i;
double r;
r = 0;
m = 0;
for( i=n; i<n+w; i++ )
{
if( i < 0 )
break;
f = Ga[i];
if( !f )
break;
if( f->r < r )
{
r = f->r;
m = i;
}
}
return( m );
}
/*
* yait_ffmax:
*/
static int
yait_ffmax( int n, int w )
{
Fi *f;
int m, i;
double r;
r = 0;
m = 0;
for( i=n; i<n+w; i++ )
{
if( i < 0 )
break;
f = Ga[i];
if( !f )
break;
if( f->r > r )
{
r = f->r;
m = i;
}
}
return( m );
}
/*
* yait_m5:
*/
static int
yait_m5( int n )
{
while( n < 0 )
n += 5;
return( n % 5 );
}
/*
* yait_mark_grp:
* Try to catch the situation where a progressive frame is missing
* between interlace groups. This will cause an erroneous (opposite) ip
* pattern to be detected. The first sequence shown below is a normal (odd)
* telecine pattern. The second shows what happens when a progressive frame
* is missing. We want to reject the even pattern detected. Therefore, if
* we find an identical pattern at n+4 we keep it. If not, we reject if an
* opposite pattern follows at n+2 of greater weight.
*
* n: 0 1 2 3 4 0 1 2 3 4
* r: 0 -1 0 1 0 0 -1 0 1 0
* odd odd
*
* n: 0 1 2 3 4 1 2 3 4
* r: 0 -1 0 1 0 -1 0 1 0
* odd even odd
*/
static void
yait_mark_grp( int p, int n, double w )
{
Fi *f;
double nw;
int np, t, i;
if( n%5 != (p+2)%5 )
return;
/* only overwrite an existing pattern if weight is greater */
f = Ga[n];
if( w <= f->w )
return;
/* check for same pattern at n+4 */
if( p < 10 )
np = yait_find_odd( OThresh, n+4, NULL, 5 );
else
np = yait_find_even( EThresh, n+4, NULL, 5 );
if( np < 0 )
{
/* no pattern at n+4, reject if opposite ip at n+2 */
if( p < 10 )
np = yait_find_even( EThresh, n+2, &nw, 5 );
else
np = yait_find_odd( OThresh, n+2, &nw, 5 );
if( np>=0 && nw>w )
return;
}
/* erase previous pattern */
if( n > 1 )
{
Ga[n-1]->op = 0;
Ga[n-2]->op = 0;
}
/* this frame and next are interlaced */
t = (p < 10) ? Y_OP_ODD : Y_OP_EVEN;
f->op = t | Y_OP_SAVE | Y_OP_DROP;
f = Ga[n+1];
f->op = t | Y_OP_COPY;
/* assume 1 progressive on either side of the tuplet */
for( i=n-1; i<n+4; i++ )
{
if( i<0 || i>=Ng )
continue;
f = Ga[i];
f->ip = p;
f->w = w;
}
}
/*
* yait_find_drops:
* For every group of 5 frames, make sure we drop a frame. Allow up to a
* DropWin (default 15) group lookahead to make up for extra or missing drops. (The
* duplicated frames generated by --hard_fps can be quite early or late in the sequence).
* If a group requires a drop, but none exists, mark the group as requiring de-interlacing.
* Finally, consequetive marked groups inherit surrounding interleave patterns.
*
* Each group will receive one of the following flags:
*
* Y_HAS_DROP - group has a single drop frame
* Y_BANK_DROP - extra drop, can be used forward
* Y_WITHDRAW_DROP - missing drop, use banked drop from behind
* Y_RETURN_DROP - extra drop, can be used behind
* Y_BORROW_DROP - missing drop, use future extra drop
* Y_FORCE_DEINT - force de-interlacing, (produces a drop)
* Y_FORCE_DROP - missing drop, no extras and no interleave found
* Y_FORCE_KEEP - extra drop, no consumer so have to keep it
*
* For any flags other than FORCE, no action is required. Eeach group already has
* an available frame to drop, whether a marked duplicate, or a locally detected
* interleave pattern (which produces a drop).
*
* For Y_FORCE_DEINT, assemble consecutive groups of this type and try to inherit
* adjacent interleave patterns. If no pattern is available, mark them as
* Y_FORCE_DROP.
*/
static void
yait_find_drops( void )
{
Fi *f;
int d, l;
/* populate drop counts */
for( d=0; d<Nd; d++ )
Da[d] = yait_cnt_drops( d*5 );
/* balance drop counts restricted by window size */
for( d=0; d<Nd; d++ )
{
f = Fa[d*5];
/* this is what we want to see */
if( Da[d] == 1 )
{
if( !f->gf )
f->gf = Y_HAS_DROP;
continue;
}
/* group is missing a drop? */
if( !Da[d] )
{
/* look ahead for an extra drop */
l = yait_extra_drop( d );
if( l )
{
/* found one */
Da[d]++;
f->gf = Y_BORROW_DROP;
--Da[l];
Fa[l*5]->gf = Y_RETURN_DROP;
continue;
}
/* no extra drops exist, mark for de-interlacing */
f->gf = Y_FORCE_DEINT;
continue;
}
/* we have too many drops */
while( Da[d] > 1 )
{
--Da[d];
/* look ahead for a missing drop */
l = yait_missing_drop( d );
if( l )
{
/* found one */
f->gf = Y_BANK_DROP;
Da[l]++;
Fa[l*5]->gf = Y_WITHDRAW_DROP;
continue;
}
/* we have to keep a drop */
if( !NoKeeps )
{
f->gf = Y_FORCE_KEEP;
yait_keep_frame( d*5 );
Stat_fk++;
}
}
}
}
/*
* yait_cnt_drops:
*/
static int
yait_cnt_drops( int n )
{
Fi *f;
int d, i;
d = 0;
for( i=n; i<n+5 && i<Nf; i++ )
{
f = Fa[i];
if( f->drop || f->op&Y_OP_DROP )
d++;
}
return( d );
}
/*
* yait_extra_drop:
* Scan DropWin groups ahead for an extra drop.
*/
static int
yait_extra_drop( int d )
{
int l, w;
for( w=0; w<DropWin; w++ )
{
l = d + w + 1;
if( l >= Nd )
return( 0 );
if( Da[l] > 1 )
return( l );
}
return( 0 );
}
/*
* yait_missing_drop:
* Scan DropWin groups ahead for a missing drop.
*/
static int
yait_missing_drop( int d )
{
int l, w;
for( w=0; w<DropWin; w++ )
{
l = d + w + 1;
if( l >= Nd )
return( 0 );
if( !Da[l] )
return( l );
}
return( 0 );
}
/*
* yait_keep_frame:
* Multiple drops exist. Pick the best frame to keep. This can be difficult,
* as we do not want to keep a duplicate of an interlaced frame. First, try to find
* a hard dropped frame which does not follow an interlace. If one can be found, then
* simply negate the drop flag. If we are duplicating an interlace, alter the frame
* operations for the group to produce a non-interlaced duplicate.
*/
static void
yait_keep_frame( int n )
{
Fi *f;
int da[6], bd, d, i;
d = yait_get_hdrop( n, da );
if( !d )
{
/* no hard drop frames were found, so ... */
/* two interlace drops exist, keep one, but blend it */
for( i=n; i<n+5 && i<Nf; i++ )
{
f = Fa[i];
if( f->op & Y_OP_DROP )
{
f->op &= ~Y_OP_DROP;
f->op |= Y_OP_DEINT;
return;
}
}
/* sanity check */
f = Fa[n];
fprintf( stderr, "No drop frame can be found, frame: %d\n", f->fn );
exit( 1 );
}
/* try to use a drop frame that isn't an interlace duplicate */
bd = -1;
for( i=0; i<5; i++ )
{
d = da[i];
if( !d )
/* can't access before Fa[0] */
continue;
if( d < 0 )
/* end of drop list */
break;
f = Fa[d-1];
if( f->drop )
/* two dups in a row */
f = Fa[d-2];
if( !f->op )
{
/* good */
f = Fa[d];
f->drop = FALSE;
return;
}
if( f->op & Y_OP_COPY )
bd = d;
}
/* keeping a duplicate of an interlace, try to use one which duplicates the */
/* second of an interlace pair, as that is cleaner to deal with */
/* bd (best drop) was set earlier in the loop if such a frame was found */
if( bd < 0 )
bd = da[0];
yait_ivtc_keep( bd );
}
/*
* yait_get_hdrop:
* Populate an index array of the hard dropped frames, and return
* the count of how many were found.
*/
static int
yait_get_hdrop( int n, int *da )
{
Fi *f;
int d, i;
d = 0;
for( i=n; i<n+5 && i<Nf; i++ )
{
f = Fa[i];
if( f->drop )
{
*da++ = i;
d++;
}
}
*da = -1;
return( d );
}
/*
* yait_ivtc_keep
* Depending upon the position of the DROP in the pattern, alter the
* frame ops to generate a non-interlaced frame, and keep it.
*
* Case 1:
* If the duplicated frame is the second of the interlaced pair, then
* simply repeat the row copy operation and keep the frame.
*
* Original (odd pattern):
* sd c
* even: 2 2 3 3 4
* odd: 2 3 4 4 4
* drop DROP
*
* yeilds (bad keep frame):
* even: 2 3 3 4
* odd: 2 3 4 4
* KEEP
* Revised:
* sd c c
* even: 2 2 3 3 4
* odd: 2 3 4 4 4
* drop DROP
* yeilds:
* even: 2 3 3 4
* odd: 2 3 3 4
* KEEP
*
* Case 2:
* If the duplicated frame copies the first of the interlaced pair, more
* work must be done:
*
* Original (odd pattern):
* sd c
* even: 2 2 2 3 4
* odd: 2 3 3 4 4
* drop DROP
*
* yeilds (bad keep frame):
* even: 2 2 3 4
* odd: 2 3 3 4
* KEEP
* Revised:
* s c sd c
* even: 2 2 2 3 4
* odd: 2 3 3 4 4
* drop
* yeilds:
* even: 2 2 3 4
* odd: 2 2 3 4
* (keep)
*/
static void
yait_ivtc_keep( int d )
{
Fi *fd, *fp;
int t;
fd = Fa[d];
fp = Fa[d-1];
if( fp->drop )
fp = Fa[d-2];
if( fp->op & Y_OP_COPY )
{
/* case 1 */
fd->op = fp->op;
fd->drop = FALSE;
return;
}
/* case 2 */
if( d < 2 )
{
/* can't access before Fa[0] */
/* (unlikely we would see this the first two frames of a film) */
fd->drop = FALSE;
return;
}
fd->op = fp->op;
fd->drop = FALSE;
t = fp->op & Y_OP_PAT;
fp->op = t | Y_OP_COPY;
fp = Fa[d-2];
fp->op = t | Y_OP_SAVE;
}
/*
* yait_ivtc_grps:
* For each group missing an interleave pattern, scan backward and forward
* for an adjacent pattern. Consider hard dropped frames as barriers. If two
* different patterns exist, test the pattern against the original r values to find
* the best match. For consecutive (forced) interleave groups, use the previously
* found pattern values, until the forward scan value is used, which is then
* propagated to the rest of the sequence. (This avoids an O(n^2) search).
*
* If no pattern can be found, force a drop of a frame in the group.
*
* TODO:
* I should really be detecting scene changes as well, and consider them
* barriers.
*/
static void
yait_ivtc_grps( void )
{
Fi *f;
int pb, pf, fg;
int p, n;
/* process by groups of 5 */
fg = TRUE;
pb = -1;
pf = -1;
for( n=0; n<Nf; n+=5 )
{
f = Fa[n];
if( f->gf != Y_FORCE_DEINT )
{
fg = TRUE;
continue;
}
if( fg )
{
/* this is the first group of a sequence, scan */
fg = FALSE;
pb = yait_scan_bk( n );
pf = yait_scan_fw( n );
}
if( pb<0 && pf<0 )
{
/* no pattern exists */
f->gf = Y_FORCE_DROP;
yait_drop_frame( n );
continue;
}
/* deinterlace the group with one of the given patterns */
/* if the pattern used is forward, keep it from now on */
p = yait_ivtc_grp( n, pb, pf );
if( p < 0 )
{
/* no pattern will match */
f->gf = Y_FORCE_DROP;
yait_drop_frame( n );
continue;
}
if( p == pf )
pb = -1;
}
}
/*
* yait_scan_bk:
*/
static int
yait_scan_bk( int n )
{
Fi *f;
int i;
for( i=n-1; i>=0; --i )
{
f = Fa[i];
if( !f )
return( -1 );
if( f->drop )
return( -1 );
if( f->ip != -1 )
return( f->ip );
}
return( -1 );
}
/*
* yait_scan_fw:
*/
static int
yait_scan_fw( int n )
{
Fi *f;
int i;
for( i=n+5; i<Nf; i++ )
{
f = Fa[i];
if( f->drop )
return( -1 );
if( f->ip != -1 )
return( f->ip );
}
return( -1 );
}
/*
* yait_drop_frame:
* Choose a frame to drop. We want the frame with the highest fabs(r) value,
* as it is likely an interlaced frame. Do not use a frame which follows an assigned
* ip pattern, (it is the trailing element of a tuplet). If no r values exceed the
* threshold, choose the frame with the minimum delta.
*
* Frame: 0 1 2 3 4 | 5 6 7 8 9
* Ratio: 0 0 0 -1 0 | 1 0 0 0 0
* Op: sd c |
* group boundary
*
* In the above example, the first frame of the second group (5) may have the highest
* ratio value, but is the worst choice because it is part of the detected pattern and
* is a unique progressive frame.
*/
static void
yait_drop_frame( int n )
{
Fi *f;
double mr, r;
int md, d;
int fr, fd;
int i;
mr = 0;
md = 0;
fr = n;
fd = n;
for( i=n; i<n+5 && i<Nf-1; i++ )
{
if( !i )
/* can't access before Fa[0] */
continue;
if( Fa[i-1]->drop || Fa[i+1]->drop )
/* avoid two consequetive drops */
continue;
if( Fa[i-1]->op & Y_OP_PAT )
/* trailing tuplet element */
continue;
f = Fa[i];
r = fabs( f->ro );
if( r > mr )
{
mr = r;
fr = i;
}
d = f->ed + f->od;
if( !md || d<md )
{
md = d;
fd = i;
}
}
Fa[ (mr>Thresh)?fr:fd ]->drop = TRUE;
Stat_fd++;
}
/*
* yait_ivtc_grp:
* We need to de-interlace this group. Given are two potential patterns.
* If both are valid, test both and keep the one with the best r value matches.
* For the pattern used, mark the group, set the frame ops accordingly, and return
* it as the function value.
*/
static int
yait_ivtc_grp( int n, int p1, int p2 )
{
Fi *f;
double thresh, m1, m2;
int p, t, i;
m1 = (p1 < 0) ? -1 : yait_tst_ip(n,p1);
m2 = (p2 < 0) ? -1 : yait_tst_ip(n,p2);
/* yait_tst_ip() returns the sum of two ratios */
/* we want both ratios > Y_MTHRESH */
thresh = Y_MTHRESH * 2;
if( !NoDrops && m1<thresh && m2<thresh )
/* neither pattern matches, force a drop instead */
return( -1 );
p = (m1 > m2) ? p1 : p2;
/* sanity check */
if( p < 0 )
{
f = Fa[n];
fprintf( stderr, "Impossible interlace pattern computed (%d), frame: %d\n",
p, f->fn );
exit( 1 );
}
/* we have a pattern, mark group */
for( i=n; i<n+5 && i<Nf; i++ )
{
f = Fa[i];
if( f->drop )
{
fprintf( stderr,
"De-interlace, horribly confused now, frame: %d.\n", f->fn );
exit( 1 );
}
f->ip = p;
}
f = Fa[n];
n = f->gi;
/* sanity check */
if( Ga[n] != f )
{
fprintf( stderr, "Lost our frame in the group array, frame: %d\n", f->fn );
exit( 1 );
}
t = (p < 10) ? Y_OP_ODD : Y_OP_EVEN;
for( i=n; i<n+5 && i<Ng-1; i++ )
{
if( i%5 == (p+2)%5 )
{
f = Ga[i];
f->op = t | Y_OP_SAVE | Y_OP_DROP;
/* don't overwrite an existing frame drop */
f = Ga[i+1];
if( !(f->op&Y_OP_DROP) )
f->op = t | Y_OP_COPY;
break;
}
}
return( p );
}
/*
* yait_tst_ip:
*/
static double
yait_tst_ip( int n, int p )
{
double rs, r;
int s, i;
s = (p < 10) ? 1 : -1;
rs = 0;
n = Fa[n]->gi;
for( i=n; i<n+5 && i<Ng-2; i++ )
{
if( i%5 != (p+2)%5 )
continue;
/* strong pattern would have r[i]<-thresh and r[i+2]>thresh */
r = s * Ga[i]->ro;
if( r < 0 )
rs += fabs( r );
r = s * Ga[i+2]->ro;
if( r > 0 )
rs += r;
break;
}
return( rs );
}
/*
* yait_deint:
* For non 3/2 telecine patterns, we may have let interlaced frames
* through. Tell transcode to de-interlace (blend) these. This is the case for
* any frame having a high ratio with no interlace pattern detected.
*
* TODO:
* This was an afterthought. Perhaps we can avoid a 32detect pass on
* the video by performing this, although it is difficult to detect out of
* pattern interlace frames solely on row delta information. Perhaps we should
* have built 32detect into the log generation, and added an extra flag field if
* we thought the frame was interlaced. This also would help when trying to
* assign ambiguous ip patterns.
*/
static void
yait_deint( void )
{
Fi *fp, *fn, *f;
int i;
for( i=1; i<Ng-1; i++ )
{
fp = Ga[i-1];
f = Ga[i];
fn = Ga[i+1];
if( f->op&Y_OP_PAT || f->drop )
/* already being de-interlaced or dropped */
continue;
if( fp->op & Y_OP_PAT )
/* trailing element of a tuplet */
continue;
if( fabs(f->r) < Blend )
/* it isn't interlaced (we think) */
continue;
if( f->nd < Y_FWEIGHT )
/* delta is too weak, interlace is likely not visible */
continue;
if( fp->nd>Y_SCENE_CHANGE || f->nd>Y_SCENE_CHANGE )
/* can't make a decision over scene changes */
continue;
/* this frame is interlaced with no operation assigned */
f->op = Y_OP_DEINT;
/* if the next frame ratio < thresh, it is similar, unless */
/* a scene change, therefore interlaced as well */
if( fabs(fn->r)<Thresh && fn->nd<Y_SCENE_CHANGE )
if( !(fn->op&Y_OP_PAT) && !fn->drop )
fn->op = Y_OP_DEINT;
/* if the next frame(s) are duplicates of this, mark them */
/* for blending as well, as the may eventually be force kept */
while( f->next && !f->next->gi )
{
f = f->next;
f->op = Y_OP_DEINT;
}
}
}
/*
* yait_write_ops:
*/
static void
yait_write_ops( void )
{
Fi *f;
for( f=Fl; f; f=f->next )
fprintf( OpsFp, "%d: %s\n", f->fn, yait_write_op(f) );
}
/*
* yait_write_op:
*/
static char*
yait_write_op( Fi *f )
{
static char buf[10];
char *p;
int op;
p = buf;
if( f->drop )
{
*p++ = 'd';
*p = 0;
Stat_nd++;
return( buf );
}
op = f->op;
if( op & Y_OP_ODD )
*p++ = 'o';
if( op & Y_OP_EVEN )
*p++ = 'e';
if( op & Y_OP_SAVE )
*p++ = 's';
if( op & Y_OP_COPY )
*p++ = 'c';
if( op & Y_OP_DROP )
{
*p++ = 'd';
Stat_nd++;
if( op & Y_OP_ODD )
Stat_no++;
else
Stat_ne++;
}
if( op & Y_OP_DEINT )
{
*p++ = '0' + DeintMode;
Stat_nb++;
}
*p = 0;
return( buf );
}
/*
* yait_fini:
* Free up allocations.
*/
static void
yait_fini( void )
{
int i;
for( i=0; i<Nf; i++ )
free( Fa[i] );
free( Fa );
free( Ga );
free( Da );
}
/*
* Output debug information to stdout
*/
static void
yait_debug_fi( void )
{
Fi *f;
int i;
printf( "Options:\n" );
printf( "\tLog file: %s\n", LogFn );
printf( "\tOps file: %s\n", OpsFn );
printf( "\tEven Threshold: %g\n", EThresh );
printf( "\tOdd Threshold: %g\n", OThresh );
printf( "\tBlend threshold: %g\n", Blend );
printf( "\tDrop window size: %d\n", DropWin );
printf( "\tDe-interlace mode: %d\n\n", DeintMode );
printf( "Stats:\n" );
printf( "\tTotal number of frames: %d\n", Nf );
printf( "\tNumber of frames divided by 5: %d\n", Nf/5 );
printf( "\tTotal dropped frames: %d\n", Stat_nd );
printf( "\tTotal blended frames: %d\n", Stat_nb );
printf( "\tTotal odd interlaced pairs: %d\n", Stat_no );
printf( "\tTotal even interlaced pairs: %d\n", Stat_ne );
printf( "\tNumber of forced frame drops: %d\n", Stat_fd );
printf( "\tNumber of forced frame keeps: %d\n\n", Stat_fk );
printf( "\tMax row delta: %d\n\n", Md );
i = 0;
for( f=Fl; f; f=f->next, i++ )
{
if( i && !(i%5) )
printf( "\n" );
printf( "Frame %6d: e: %8d, o: %8d, r: %7.3f, ro: %7.3f, w: %8.4f, "
"ip: %2d, gi: %6d, op: %-4s d: %s %s\n",
f->fn, f->ed, f->od, f->r, f->ro, f->w, f->ip, f->gi,
yait_op(f->op), yait_drop(f), yait_grp(f->gf) );
}
}
static char*
yait_op( int op )
{
static char buf[10];
char *p;
p = buf;
*p = 0;
if( !op )
return( buf );
if( op & Y_OP_ODD )
*p++ = 'o';
if( op & Y_OP_EVEN )
*p++ = 'e';
if( op & Y_OP_SAVE )
*p++ = 's';
if( op & Y_OP_COPY )
*p++ = 'c';
if( op & Y_OP_DROP )
*p++ = 'd';
if( op & Y_OP_DEINT )
*p++ = '0' + DeintMode;
*p = 0;
return( buf );
}
static char*
yait_drop( Fi *f )
{
if( f->drop )
return( "DROP" );
if( f->op&Y_OP_ODD && f->op&Y_OP_DROP )
return( "odd " );
if( f->op&Y_OP_EVEN && f->op&Y_OP_DROP )
return( "even" );
return( " " );
}
static char*
yait_grp( int flg )
{
switch( flg )
{
case Y_HAS_DROP:
return( "has drop" );
case Y_BANK_DROP:
return( "bank" );
case Y_WITHDRAW_DROP:
return( "withdraw" );
case Y_BORROW_DROP:
return( "borrow" );
case Y_RETURN_DROP:
return( "return" );
case Y_FORCE_DEINT:
return( "force deint" );
case Y_FORCE_DROP:
return( "force drop" );
case Y_FORCE_KEEP:
return( "force keep" );
}
return( "" );
}