[MPlayer-dev-eng] [PATCH] A filter to inverse-telecine deinterlaced video

Ville Saari 114263 at foo.bar.org
Mon Apr 5 22:49:36 CEST 2004


Encouraged by the acceptance of vf_phase filter I decided to post
my other special filter too: vf_divtc

It removes the duplicate frames that are the result of doing
one-field-only video capture for telecined film. Many cif-resolution
mpeg files around have this problem (and NTSC VideoCDs). It can also
be used to repair video which has been incorrectly deinterlaced when it
should have been inverse-telecined.

The vf_decimate could be used to do the same, but if the video
is noisy or has compression artifacts, it's very difficult to
parametrize it to differentiate between noisy duplicates
and real frames with little motion. Whenever it makes wrong decision
and mencoder's frame-skipping/duplicating compansates, the result
is juddering that is much worse than the original problem.

This filter assumes normal 3:2 telecine pattern, which becomes
2:1:1:1-pattern in one-field representation. It also uses large context
to decide which frames are duplicates and drops exactly one of every
five frames. By default it examines the last 30 frames to decide how
the 2:1:1:1-pattern fits best. If the pattern is not visible enough,
it locks to the last pattern it has seen.

The result is quite good if the telecine pattern is regular, but the
inertia caused by the long window will make the filter do a few mis-drops
whenever the phase of the pattern changes. This is visible as a momentary
juddering and if it happens often, it can be quite annoying. This can be
avoided by using the filter in two-pass mode. First pass will gather
statistics to a log file and then the second pass will use that data to
see the future so it can center its window around the current frame.
Two-pass mode also does some special magic whenever the phase changes to
determine the exact location of the change.

If the filter is used to fix incorrectly deinterlaced progressive films,
then the method described above only works if the deinterlacing method
was asymmetric: keeping one field and completely or partially interpolating
the other (like pp=li, pp=ci or pp=md). If the deinterlacer was symmetric
(like pp=lb or pp=l5), then the result will not have duplicates, but
instead will have three clean frames followed by two frames with ghosting.
This obviously can not be fixed by dropping every fifth frame, but the
filter also has a special mode for such video:

In the deghosting mode it will search for this peculiar pattern instead of
duplicate frames and then replaces the two ghost frames by a single
reconstructed frame. The reconstruction argorithm is to drop the second
ghost frame and replace the first ghost frame by itself times two minus
the previous clean frame. This works because the ghost frame is the average
of the previous clean frame and the original frame between the two ghost
frames. This will amplify noise and compression artifacts in the ghost
frame, which can cause 6Hz flicker in flat surfaces, so I added a threshold
parameter that determines the minimum difference between the ghost frame
and the previous frame that each pixel must have to be deghosted. This
limits the deghosting to the moving parts of the image only.

Better deghosting algorithm would be to sum both ghost frames together
and subtract the average of the previous and the next clean frames.
However this would mean that the output of the deghosted frame must be
delayed until the next clean frame is seen and this caused mencoder's
frame skipping/duplicating mechanism to go haywire when I tried it.
Any ideas how to avoid this?

-- 
 Ville
-------------- next part --------------
diff -Naur main/DOCS/man/en/mplayer.1 patched/DOCS/man/en/mplayer.1
--- main/DOCS/man/en/mplayer.1	2004-03-30 18:23:31.000000000 +0300
+++ patched/DOCS/man/en/mplayer.1	2004-04-05 23:13:16.000000000 +0300
@@ -3136,6 +3136,61 @@
 Currently only libmpeg2 exports the needed flags.
 If used on material that does not set them, the filter does nothing.
 .TP
+.B divtc[=options]
+Inverse telecine for deinterlaced video.
+If 3:2-pulldown telecined video has lost one of the fields or is deinterlaced
+using a method that keeps one field and interpolates the other, the result is
+a juddering video that has every fourth frame duplicated.
+This filter is intended to find and drop those duplicates and restore the
+original film frame rate.
+When using this filter, you must specify -ofps that is 4/5 of the fps of the
+input file (23.976 if the input is 29.97fps).
+The options are:
+.RSs
+.IPs pass=1|2
+Use two-pass mode.
+This produces best results.
+Pass 1 analyzes the video and writes the results to a log file.
+Pass 2 then reads this log file and uses the information to do the actual work.
+.IPs file=filename
+Set the 2-pass log file name (default: "framediff.log").
+.IPs threshold=value
+Set the minimum strength the telecine pattern must have for the filter to
+believe in it (default: 0.5).
+This is used to avoid recognizing false pattern from the parts of the video
+that are very dark or very still.
+.IPs window=numframes 
+Set the number of past frames to look at when searching for pattern
+(default: 30).
+Longer window improves the reliability of the pattern search, but shorter
+window improves the reaction time to the changes in the telecine phase.
+This only affects the one-pass mode.
+The two-pass mode currently uses fixed window that extends to both future
+and past.
+.IPs phase=0|1|2|3|4
+Sets the initial telecine phase for one-pass mode (default: 0).
+The two-pass mode can see the future, so it is able to use the correct
+phase from the beginning, but one-pass mode can only guess.
+It catches the correct phase when it finds it, but this option can be used
+to fix the possible juddering at the beginning.
+The first pass of the two-pass mode also uses this, so if you save the output
+from the first pass, you get constant phase result.
+.IPs deghost=value
+Set the deghosting threshold (0-255 for one-pass mode, -255-255 for two-pass
+mode, default 0).
+If nonzero, deghosting mode is used.
+This is for video that has been deinterlaced by blending the fields
+together instead of dropping one of the fields.
+Deghosting amplifies any compression artifacts in the blended frames, so the
+parameter value is used as a threshold to exclude those pixels from
+deghosting that differ from the previous frame less than specified value.
+If two-pass mode is used, then negative value can be used to make the
+filter analyze the whole video in the beginning of pass-2 to determine
+whether it needs deghosting or not and then select either zero or the
+absolute value of the parameter.
+Specify this option for pass-2, it makes no difference on pass-1.
+.RE
+.TP
 .B phase=[t|b|p|a|u|T|B|A|U][:v]
 Delay interlaced video by one field time so that the field order
 changes.
diff -Naur main/libmpcodecs/Makefile patched/libmpcodecs/Makefile
--- main/libmpcodecs/Makefile	2004-03-30 18:23:40.000000000 +0300
+++ patched/libmpcodecs/Makefile	2004-04-05 17:14:45.000000000 +0300
@@ -14,7 +14,7 @@
 VIDEO_SRCS_OPT=vd_realvid.c vd_ffmpeg.c vd_dshow.c vd_dmo.c vd_vfw.c vd_vfwex.c vd_odivx.c vd_divx4.c vd_zrmjpeg.c vd_xanim.c vd_xvid.c vd_xvid4.c vd_libdv.c vd_qtvideo.c vd_theora.c
 VIDEO_SRCS=dec_video.c vd.c $(VIDEO_SRCS_NAT) $(VIDEO_SRCS_LIB) $(VIDEO_SRCS_OPT)
 
-VFILTER_SRCS=vf.c vf_vo.c vf_crop.c vf_expand.c vf_scale.c vf_format.c vf_noformat.c vf_yuy2.c vf_flip.c vf_rgb2bgr.c vf_rotate.c vf_mirror.c vf_palette.c vf_lavc.c vf_dvbscale.c vf_cropdetect.c vf_test.c vf_noise.c vf_yvu9.c vf_rectangle.c vf_lavcdeint.c vf_eq.c vf_eq2.c vf_halfpack.c vf_dint.c vf_1bpp.c vf_bmovl.c vf_2xsai.c vf_unsharp.c vf_swapuv.c vf_il.c vf_boxblur.c vf_sab.c vf_smartblur.c vf_perspective.c vf_down3dright.c vf_field.c vf_denoise3d.c vf_hqdn3d.c vf_detc.c vf_telecine.c vf_tfields.c vf_ivtc.c vf_ilpack.c vf_dsize.c vf_decimate.c vf_softpulldown.c vf_tinterlace.c vf_pullup.c pullup.c vf_framestep.c vf_tile.c vf_delogo.c vf_fil.c vf_hue.c vf_spp.c vf_yuvcsp.c vf_filmdint.c vf_kerndeint.c vf_rgbtest.c vf_qp.c vf_phase.c
+VFILTER_SRCS=vf.c vf_vo.c vf_crop.c vf_expand.c vf_scale.c vf_format.c vf_noformat.c vf_yuy2.c vf_flip.c vf_rgb2bgr.c vf_rotate.c vf_mirror.c vf_palette.c vf_lavc.c vf_dvbscale.c vf_cropdetect.c vf_test.c vf_noise.c vf_yvu9.c vf_rectangle.c vf_lavcdeint.c vf_eq.c vf_eq2.c vf_halfpack.c vf_dint.c vf_1bpp.c vf_bmovl.c vf_2xsai.c vf_unsharp.c vf_swapuv.c vf_il.c vf_boxblur.c vf_sab.c vf_smartblur.c vf_perspective.c vf_down3dright.c vf_field.c vf_denoise3d.c vf_hqdn3d.c vf_detc.c vf_telecine.c vf_tfields.c vf_ivtc.c vf_ilpack.c vf_dsize.c vf_decimate.c vf_softpulldown.c vf_tinterlace.c vf_pullup.c pullup.c vf_framestep.c vf_tile.c vf_delogo.c vf_fil.c vf_hue.c vf_spp.c vf_yuvcsp.c vf_filmdint.c vf_kerndeint.c vf_rgbtest.c vf_qp.c vf_phase.c vf_divtc.c
 ifeq ($(HAVE_FFPOSTPROCESS),yes)
 VFILTER_SRCS += vf_pp.c
 endif
diff -Naur main/libmpcodecs/vf.c patched/libmpcodecs/vf.c
--- main/libmpcodecs/vf.c	2004-03-30 18:23:42.000000000 +0300
+++ patched/libmpcodecs/vf.c	2004-04-05 17:14:45.000000000 +0300
@@ -88,6 +88,7 @@
 extern vf_info_t vf_info_rgbtest;
 extern vf_info_t vf_info_qp;
 extern vf_info_t vf_info_phase;
+extern vf_info_t vf_info_divtc;
 
 // list of available filters:
 static vf_info_t* filter_list[]={
@@ -169,6 +170,7 @@
     &vf_info_qp,
 #endif
     &vf_info_phase,
+    &vf_info_divtc,
     NULL
 };
 
diff -Naur main/libmpcodecs/vf_divtc.c patched/libmpcodecs/vf_divtc.c
--- main/libmpcodecs/vf_divtc.c	1970-01-01 02:00:00.000000000 +0200
+++ patched/libmpcodecs/vf_divtc.c	2004-04-05 22:20:50.000000000 +0300
@@ -0,0 +1,689 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <math.h>
+
+#include "../config.h"
+#include "../mp_msg.h"
+#include "../cpudetect.h"
+#include "../bswap.h"
+
+#include "img_format.h"
+#include "mp_image.h"
+#include "vf.h"
+
+#include "../libvo/fastmemcpy.h"
+
+vf_info_t vf_info_divtc;
+
+struct vf_priv_s
+   {
+   int deghost, pass, phase, window, fcount, bcount, frameno, misscount,
+      ocount, sum[5];
+   double threshold;
+   FILE *file;
+   char *bdata;   
+   unsigned int *csdata;
+   int *history;
+   };
+
+/*
+ * diff_MMX and diff_C stolen from vf_decimate.c
+ */
+
+#ifdef HAVE_MMX
+static int diff_MMX(unsigned char *old, unsigned char *new, int os, int ns)
+   {
+   volatile short out[4];
+   asm (
+	"movl $8, %%ecx \n\t"
+	"pxor %%mm4, %%mm4 \n\t"
+	"pxor %%mm7, %%mm7 \n\t"
+	
+	".balign 16 \n\t"
+	"1: \n\t"
+	
+	"movq (%%esi), %%mm0 \n\t"
+	"movq (%%esi), %%mm2 \n\t"
+	"addl %%eax, %%esi \n\t"
+	"movq (%%edi), %%mm1 \n\t"
+	"addl %%ebx, %%edi \n\t"
+	"psubusb %%mm1, %%mm2 \n\t"
+	"psubusb %%mm0, %%mm1 \n\t"
+	"movq %%mm2, %%mm0 \n\t"
+	"movq %%mm1, %%mm3 \n\t"
+	"punpcklbw %%mm7, %%mm0 \n\t"
+	"punpcklbw %%mm7, %%mm1 \n\t"
+	"punpckhbw %%mm7, %%mm2 \n\t"
+	"punpckhbw %%mm7, %%mm3 \n\t"
+	"paddw %%mm0, %%mm4 \n\t"
+	"paddw %%mm1, %%mm4 \n\t"
+	"paddw %%mm2, %%mm4 \n\t"
+	"paddw %%mm3, %%mm4 \n\t"
+		
+	"decl %%ecx \n\t"
+	"jnz 1b \n\t"
+	"movq %%mm4, (%%edx) \n\t"
+	"emms \n\t"
+	: 
+	: "S" (old), "D" (new), "a" (os), "b" (ns), "d" (out)
+	: "memory"
+	);
+   return out[0]+out[1]+out[2]+out[3];
+   }
+#endif
+
+static int diff_C(unsigned char *old, unsigned char *new, int os, int ns)
+   {
+   int x, y, d=0;
+
+   for(y=8; y; y--, new+=ns, old+=os)
+      for(x=8; x; x--)
+	 d+=abs(new[x]-old[x]);
+
+   return d;
+   }
+
+static int (*diff)(unsigned char *, unsigned char *, int, int);
+
+static int diff_plane(unsigned char *old, unsigned char *new,
+		      int w, int h, int os, int ns, int arg)
+   {
+   int x, y, d, max=0, sum=0, n=0;
+ 
+   for(y=0; y<h-7; y+=8)
+      {
+      for(x=0; x<w-7; x+=8)
+	 {
+	 d=diff(old+x+y*os, new+x+y*ns, os, ns);
+	 if(d>max) max=d;
+	 sum+=d;
+	 n++;
+	 }
+      }
+
+   return (sum+n*max)/2;
+   }
+
+/*
+static unsigned int checksum_plane(unsigned char *p, unsigned char *z,
+				   int w, int h, int s, int zs, int arg)
+   {
+   unsigned int shift, sum;
+   unsigned char *e;
+
+   for(sum=0; h; h--, p+=s-w)
+      for(e=p+w, shift=32; p<e;)
+	 sum^=(*p++)<<(shift=(shift-8)&31);
+
+   return sum;
+   }
+*/
+
+static unsigned int checksum_plane(unsigned char *p, unsigned char *z,
+				   int w, int h, int s, int zs, int arg)
+   {
+   unsigned int shift;
+   uint32_t sum, t;
+   unsigned char *e, *e2;
+#if MP_WORDSIZE==64
+   typedef uint64_t wsum_t;
+#else
+   typedef uint32_t wsum_t;
+#endif
+   wsum_t wsum;
+
+   for(sum=0; h; h--, p+=s-w)
+      {
+      for(shift=0, e=p+w; (int)p&(sizeof(wsum_t)-1) && p<e;)
+	 sum^=*p++<<(shift=(shift-8)&31);
+
+      for(wsum=0, e2=e-sizeof(wsum_t)+1; p<e2; p+=sizeof(wsum_t))
+	 wsum^=*(wsum_t *)p;
+
+#if MP_WORDSIZE==64
+      t=be2me_32((uint32_t)(wsum>>32^wsum));
+#else
+      t=be2me_32(wsum);
+#endif
+
+      for(sum^=(t<<shift|t>>(32-shift)); p<e;)
+	 sum^=*p++<<(shift=(shift-8)&31);
+      }
+
+   return sum;
+   }
+
+static int deghost_plane(unsigned char *d, unsigned char *s,
+			 int w, int h, int ds, int ss, int threshold)
+   {
+   int t;
+   unsigned char *e;
+
+   for(; h; h--, s+=ss-w, d+=ds-w)
+      for(e=d+w; d<e; d++, s++)
+	 if(abs(*d-*s)>=threshold)
+	    *d=(t=(*d<<1)-*s)<0?0:t>255?255:t;
+
+   return 0;
+   }
+
+static int copy_plane(unsigned char *d, unsigned char *s,
+		      int w, int h, int ds, int ss, int arg)
+   {
+   memcpy_pic(d, s, w, h, ds, ss);
+   return 0;
+   }
+
+static int imgop(int(*planeop)(unsigned char *, unsigned char *,
+			       int, int, int, int, int),
+		 mp_image_t *dst, mp_image_t *src, int arg)
+   {
+   if(dst->flags&MP_IMGFLAG_PLANAR)
+      return planeop(dst->planes[0], src?src->planes[0]:0,
+		     dst->w, dst->h,
+		     dst->stride[0], src?src->stride[0]:0, arg)+
+	     planeop(dst->planes[1], src?src->planes[1]:0,
+		     dst->chroma_width, dst->chroma_height,
+		     dst->stride[1], src?src->stride[1]:0, arg)+
+	     planeop(dst->planes[2], src?src->planes[2]:0,
+		     dst->chroma_width, dst->chroma_height,
+		     dst->stride[2], src?src->stride[2]:0, arg);
+   
+   return planeop(dst->planes[0], src?src->planes[0]:0,
+		  dst->w*(dst->bpp/8), dst->h,
+		  dst->stride[0], src?src->stride[0]:0, arg);
+   }
+
+/*
+ * Find the phase in which the telecine pattern fits best to the
+ * given 5 frame slice of frame difference measurements.
+ *
+ * If phase1 and phase2 are not negative, only the two specified
+ * phases are tested.
+ */
+
+int cmp(int *a, int *b) { return *b-*a; }
+
+static int match(struct vf_priv_s *p, int *diffs,
+		 int phase1, int phase2, double *strength)
+   {
+   static const int pattern1[]={ -4,  1, 1, 1, 1 },
+      pattern2[]={ -2, -3, 4, 4, -3 }, *pattern;
+   int f, m, n, t[5];
+
+   pattern=p->deghost>0?pattern2:pattern1;
+
+   for(f=0; f<5; f++)
+      {
+      if(phase1<0 || phase2<0 || f==phase1 || f==phase2)
+	 {	 
+	 for(n=t[f]=0; n<5; n++)
+	    t[f]+=diffs[n]*pattern[(n-f+5)%5];
+	 }
+      else
+	 t[f]=INT_MIN;
+      }
+
+   /* find the best match */
+   for(m=0, n=1; n<5; n++)
+      if(t[n]>t[m]) m=n;
+
+   if(strength)
+      {
+      /* the second best match */
+      for(f=m?0:1, n=f+1; n<5; n++)
+	 if(n!=m && t[n]>t[f]) f=n;
+
+      *strength=(t[m]>0?(double)(t[m]-t[f])/t[m]:0.0);
+      }   
+
+   return m;
+   }
+
+static int put_image(struct vf_instance_s* vf, mp_image_t *mpi)
+   {
+   mp_image_t *dmpi, *tmpi=0;
+   int n, m, f, newphase;
+   struct vf_priv_s *p=vf->priv;
+   unsigned int checksum;
+   double d;
+
+   dmpi=vf_get_image(vf->next, mpi->imgfmt,
+		     MP_IMGTYPE_STATIC, MP_IMGFLAG_ACCEPT_STRIDE |
+		     MP_IMGFLAG_PRESERVE | MP_IMGFLAG_READABLE,
+		     mpi->width, mpi->height);
+   vf_clone_mpi_attributes(dmpi, mpi);
+
+   newphase=p->phase;
+
+   switch(p->pass)
+      {
+      case 1:
+	 fprintf(p->file, "%08x %d\n",
+		 (unsigned int)imgop((void *)checksum_plane, mpi, 0, 0),
+		 p->frameno?imgop(diff_plane, dmpi, mpi, 0):0);
+	 break;
+
+      case 2:
+	 if(p->frameno/5>p->bcount)
+	    {
+	    mp_msg(MSGT_VFILTER, MSGL_ERR,
+		   "\n%s: Log file ends prematrely! "
+		   "Switching to one pass mode\n", vf->info->name);
+	    p->pass=0;
+	    break;
+	    }
+
+	 checksum=(unsigned int)imgop((void *)checksum_plane, mpi, 0, 0);
+
+	 if(checksum!=p->csdata[p->frameno])
+	    {
+	    for(f=0; f<100; f++)
+	       if(p->frameno+f<p->fcount && p->csdata[p->frameno+f]==checksum)
+		  break;
+	       else if(p->frameno-f>=0 && p->csdata[p->frameno-f]==checksum)
+		  {
+		  f=-f;
+		  break;
+		  }
+
+	    if(f<100)
+	       {
+	       mp_msg(MSGT_VFILTER, MSGL_INFO,
+		      "\n%s: Mismatch with pass-1: %+d frame(s)\n",
+		      vf->info->name, f);
+
+	       p->frameno+=f;
+	       p->misscount=0;
+	       }
+	    else if(p->misscount++>=30)
+	       {
+	       mp_msg(MSGT_VFILTER, MSGL_ERR,
+		      "\n%s: Sync with pass-1 lost! "
+		      "Switching to one pass mode\n", vf->info->name);
+	       p->pass=0;
+	       break;
+	       }
+	    }
+	 
+	 n=(p->frameno)/5;
+	 if(n>=p->bcount) n=p->bcount-1;
+
+	 newphase=p->bdata[n];
+	 break;
+	 
+      default:
+	 if(p->frameno)
+	    {
+	    int *sump=p->sum+p->frameno%5,
+	       *histp=p->history+p->frameno%p->window;
+
+	    *sump-=*histp;
+	    *sump+=(*histp=imgop(diff_plane, dmpi, mpi, 0));
+	    }
+
+	 m=match(p, p->sum, -1, -1, &d);
+
+	 if(d>=p->threshold)
+	    newphase=m;
+      }
+
+   n=p->ocount++%5;
+
+   if(newphase!=p->phase && ((p->phase+4)%5<n)==((newphase+4)%5<n))
+      {
+      p->phase=newphase;
+      mp_msg(MSGT_VFILTER, MSGL_STATUS,
+	     "\n%s: Telecine phase %d\n", vf->info->name, p->phase);
+      }
+
+   switch((p->frameno++-p->phase+10)%5)
+      {
+      case 0:
+	 imgop((void *)memcpy_pic, dmpi, mpi, 0);
+	 return 0;
+
+      case 4:
+	 if(p->deghost>0)
+	    {
+	    tmpi=vf_get_image(vf->next, mpi->imgfmt,
+			      MP_IMGTYPE_TEMP, MP_IMGFLAG_ACCEPT_STRIDE,
+			      mpi->width, mpi->height);
+	    vf_clone_mpi_attributes(tmpi, mpi);
+
+	    imgop((void *)memcpy_pic, tmpi, mpi, 0);
+	    imgop(deghost_plane, tmpi, dmpi, p->deghost);
+	    imgop((void *)memcpy_pic, dmpi, mpi, 0);
+	    return vf_next_put_image(vf, tmpi);
+	    }
+      }
+
+   imgop((void *)memcpy_pic, dmpi, mpi, 0);
+   return vf_next_put_image(vf, dmpi);
+   }
+
+int analyze(struct vf_priv_s *p)
+   {
+   int *buf=0, *bp, bufsize=0, n, b, f, i, j, m, s;
+   unsigned int *cbuf=0, *cp;
+   char *pbuf;
+   char lbuf[256];
+   int sum[5];
+   double d;
+
+   /* read the file */
+
+   n=15;
+   while(fgets(lbuf, 256, p->file))
+      {
+      if(n>=bufsize-19)
+	 {
+	 bufsize=bufsize?bufsize*2:30000;
+	 if((bp=realloc(buf, bufsize*sizeof *buf))) buf=bp;
+	 if((cp=realloc(cbuf, bufsize*sizeof *cbuf))) cbuf=cp;
+
+	 if(!bp || !cp)
+	    {
+	    mp_msg(MSGT_VFILTER, MSGL_FATAL, "%s: not enough memory\n",
+		   vf_info_divtc.name);
+	    free(buf);
+	    free(cbuf);
+	    return 0;
+	    }
+	 }
+      sscanf(lbuf, "%x %d", cbuf+n, buf+n);
+      n++;
+      }
+
+   if(!n)
+      {
+      mp_msg(MSGT_VFILTER, MSGL_FATAL, "%s: empty 2-pass log file\n",
+	     vf_info_divtc.name);
+      free(buf);
+      free(cbuf);
+      return 0;
+      }
+
+   /* generate some dummy data past the beginning and end of the array */
+
+   buf+=15, cbuf+=15;
+   n-=15;
+   
+   memcpy(buf-15, buf, 15*sizeof *buf);
+   memset(cbuf-15, 0, 15*sizeof *cbuf);
+
+   while(n%5)
+      buf[n]=buf[n-5], cbuf[n]=0, n++;
+
+   memcpy(buf+n, buf+n-15, 15*sizeof *buf);
+   memset(cbuf+n, 0, 15*sizeof *cbuf);
+
+   p->csdata=cbuf;
+   p->fcount=n;
+
+   /* array with one slot for each slice of 5 frames */
+
+   p->bdata=pbuf=malloc(p->bcount=b=(n/5));
+   memset(pbuf, 255, b);
+
+   /* resolve the automatic mode */
+
+   if(p->deghost<0)
+      {
+      int deghost=-p->deghost;
+      double s0=0.0, s1=0.0;
+
+      for(f=0; f<n; f+=5)
+	 {
+	 p->deghost=0; match(p, buf+f, -1, -1, &d); s0+=d;
+	 p->deghost=1; match(p, buf+f, -1, -1, &d); s1+=d;
+	 }
+
+      p->deghost=s1>s0?deghost:0;	 
+
+      mp_msg(MSGT_VFILTER, MSGL_INFO,
+	     "%s: Deghosting %-3s (relative pattern strength %+.2fdB)\n",
+	     vf_info_divtc.name,
+	     p->deghost?"ON":"OFF",
+	     10.0*log10(s1/s0));
+      }
+
+   /* analyze the data */
+
+   for(f=0; f<5; f++)
+      for(sum[f]=0, n=-15; n<20; n+=5)
+	 sum[f]+=buf[n+f];
+
+   for(f=0; f<b; f++)
+      {
+      m=match(p, sum, -1, -1, &d);
+
+      if(d>=p->threshold)
+	 pbuf[f]=m;      
+
+      if(f<b-1)
+	 for(n=0; n<5; n++)
+	    sum[n]=sum[n]-buf[5*(f-3)+n]+buf[5*(f+4)+n];
+      }
+
+   /* fill in the gaps */
+
+   /* the beginning */
+   for(f=0; f<b && pbuf[f]==-1; f++);
+
+   if(f==b)
+      {
+      free(buf-15);
+      mp_msg(MSGT_VFILTER, MSGL_FATAL, "%s: no telecine pattern found!\n",
+	     vf_info_divtc.name);
+      return 0;
+      }
+
+   for(n=0; n<f; pbuf[n++]=pbuf[f]);
+
+   /* the end */
+   for(f=b-1; pbuf[f]==-1; f--);
+   for(n=f+1; n<b; pbuf[n++]=pbuf[f]);
+
+   /* the rest */
+   for(f=0;;)
+      {
+      while(f<b && pbuf[f]!=-1) f++;
+      if(f==b) break;
+      for(n=f; pbuf[n]==-1; n++);
+
+      if(pbuf[f-1]==pbuf[n])
+	 {
+	 /* just a gap */
+	 while(f<n) pbuf[f++]=pbuf[n];
+	 }
+      else
+	 {
+	 /* phase change, reanalyze the original data in the gap with zero
+	    threshold for only the two phases that appear at the ends */
+
+	 for(i=0; i<5; i++)
+	    for(sum[i]=0, j=5*f-15; j<5*f; j+=5)
+	       sum[i]+=buf[i+j];
+
+	 for(i=f; i<n; i++)
+	    {
+	    pbuf[i]=match(p, sum, pbuf[f-1], pbuf[n], 0);
+
+	    for(j=0; j<5; j++)
+	       sum[j]=sum[j]-buf[5*(i-3)+j]+buf[5*(i+4)+j];
+	    }
+
+	 /* estimate the transition point by dividing the gap
+	    in the same proportion as the number of matches of each kind */
+
+	 for(i=f, m=f; i<n; i++)
+	    if(pbuf[i]==pbuf[f-1]) m++;
+
+	 /* find the transition of the right direction nearest to the
+	    estimated point */
+
+	 if(m>f && m<n)
+	    {
+	    for(j=m; j>f; j--)
+	       if(pbuf[j-1]==pbuf[f-1] && pbuf[j]==pbuf[n]) break;
+	    for(s=m; s<n; s++)
+	       if(pbuf[s-1]==pbuf[f-1] && pbuf[s]==pbuf[n]) break;
+
+	    m=(s-m<m-j)?s:j;
+	    }
+
+	 /* and rewrite the data to allow only this one transition */
+
+	 for(i=f; i<m; i++)
+	    pbuf[i]=pbuf[f-1];
+
+	 for(; i<n; i++)
+	    pbuf[i]=pbuf[n];
+
+	 f=n;
+	 }
+      }
+
+   free(buf-15);
+
+   return 1;
+   }
+
+static void uninit(struct vf_instance_s* vf)
+   {
+   if(vf->priv)
+      {
+      if(vf->priv->file) fclose(vf->priv->file);
+      if(vf->priv->csdata) free(vf->priv->csdata-15);
+      free(vf->priv->bdata);
+      free(vf->priv->history);
+      free(vf->priv);
+      }
+   }
+
+static int open(vf_instance_t *vf, char* args)
+   {
+   struct vf_priv_s *p;
+   char *filename="framediff.log", *ap, *q, *a;
+
+   if(args && !(args=strdup(args)))
+      {
+   nomem:
+      mp_msg(MSGT_VFILTER, MSGL_FATAL,
+	     "%s: not enough memory\n", vf->info->name);
+   fail:
+      uninit(vf);
+      free(args);
+      return 0;
+      }
+
+   vf->put_image=put_image;
+   vf->uninit=uninit;
+   vf->default_reqs=VFCAP_ACCEPT_STRIDE;
+   if(!(vf->priv=p=calloc(1, sizeof(struct vf_priv_s))))
+      goto nomem;
+
+   p->phase=5;   
+   p->threshold=0.5;
+   p->window=30;
+
+   if((ap=args))
+      while(*ap)
+	 {
+	 q=ap;
+	 if((ap=strchr(q, ':'))) *ap++=0; else ap=q+strlen(q);
+	 if((a=strchr(q, '='))) *a++=0; else a=q+strlen(q);
+      
+	 switch(*q)
+	    {
+	    case 0:                              break;
+	    case 'f': filename=a;                break;
+	    case 't': p->threshold=atof(a);      break;
+	    case 'w': p->window=5*(atoi(a)+4)/5; break;
+	    case 'd': p->deghost=atoi(a);        break;
+	    case 'p':
+	       if(q[1]=='h') p->phase=atoi(a);
+	       else p->pass=atoi(a);
+	       break;
+
+	    case 'h':
+	       mp_msg(MSGT_VFILTER, MSGL_INFO,
+		      "\n%s options:\n\n"
+		      "pass=1|2         - Use 2-pass mode.\n"
+		      "file=filename    - Set the 2-pass log file name "
+		      "(default %s).\n"
+		      "threshold=value  - Set the pattern recognition "
+		      "sensitivity (default %g).\n"
+		      "deghost=value    - Select deghosting threshold "
+		      "(default %d).\n"
+		      "window=numframes - Set the statistics window "
+		      "for 1-pass mode (default %d).\n"
+		      "phase=0|1|2|3|4  - Set the initial phase "
+		      "for 1-pass mode (default %d).\n\n"
+		      "The option names can be abbreviated to the shortest "
+		      "unique prefix.\n\n",
+		      vf->info->name, filename, p->threshold, p->deghost,
+		      p->window, p->phase%5);
+	       break;
+	       
+	    default:
+	       mp_msg(MSGT_VFILTER, MSGL_FATAL,
+		      "%s: unknown argument %s\n", vf->info->name, q);	       
+	       goto fail;
+	    }
+	 }
+   
+   switch(p->pass)
+      {
+      case 1:
+	 if(!(p->file=fopen(filename, "w")))
+	    {
+	    mp_msg(MSGT_VFILTER, MSGL_FATAL,
+		   "%s: can't create file %s\n", vf->info->name, filename);
+	    goto fail;
+	    }
+
+	 break;
+	 
+      case 2:
+	 if(!(p->file=fopen(filename, "r")))
+	    {
+	    mp_msg(MSGT_VFILTER, MSGL_FATAL,
+		   "%s: can't open file %s\n", vf->info->name, filename);
+	    goto fail;
+	    }
+
+	 if(!analyze(p))
+	    goto fail;
+
+	 fclose(p->file);
+	 p->file=0;
+	 break;
+      }
+   
+   if(p->window<5) p->window=5;
+   if(!(p->history=calloc(sizeof *p->history, p->window)))
+      goto nomem;
+
+   diff=
+#ifdef HAVE_MMX
+      gCpuCaps.hasMMX?diff_MMX:
+#endif
+      diff_C;
+
+   free(args);
+   return 1;
+   }
+
+vf_info_t vf_info_divtc =
+   {
+   "inverse telecine for deinterlaced video",
+   "divtc",
+   "Ville Saari",
+   "",
+   open,
+   NULL
+   };


More information about the MPlayer-dev-eng mailing list