[MPlayer-dev-eng] [PATCH] New filter vf_ivtc, smart dropping of every 5th frame

Johannes Guenther johannes.guenther at s1999.tu-chemnitz.de
Sun Feb 16 04:35:06 CET 2003


Hi,
after using mencoder for awhile I came across movies with (real)
29.97 fps which were created from a 24 fps source by inserting a
duplicate frame after every 4th frame. When (re)encoding these movies it
would be clever to drop these duplicated frames to save bandwidth and to
eliminate the jerky motion artefacts.

But to only use the option '-ofps 23.976' is not satisfactory because
mencoder bluntly drops every 5th frame regardless of being duplicates
or not; or missed I something?

So I wrote a filter which does the job - other programs like transcode
or VirtualDup already have one. There have also been requests concerning
this issue:
http://mplayerhq.hu/pipermail/mplayer-users/2002-September/020628.html
http://mplayerhq.hu/pipermail/mplayer-dev-eng/2002-January/004502.html

Some implementation details; how I see this problem; things I would like
to discuss:
- To detect the duplicated frames it is necessary to buffer at least 6
  frames (you cannot display/encode the frames and decide afterwards
  'Oh, I wanted to skip that frame'). But buffering (at least of this
  amount) is not supported by the vf-API (am I right?). So I had to
  expand it by introducing a new image-type MP_IMGTYPE_BUF.
- Unfortunately I have to memcpy the whole frame to the buffer. It would
  be better to store only the meta-data/image-structure but this did not
  work (the information was lost during two calls of 'put_image').
- You need to also specify the '-mc 0' and '-noskip' option to mencoder
  otherwise mencoder complains about missing the first frames (which are
  gone to the buffer and were not yet displayed) and tries to insert
  frames.
- 'put_image' is not noticed whether this is the last time it is called,
  but it needs to flush the buffer then, otherwise the last frames are
  lost. This cannot be done in 'uninit' because then everything is 
  already over (files closed, image-structures invalid).
  The hack is to provide the total frame number as a hint to vf_ivtc via
  commandline, thus 'put_image' knows when it is its last time.
- All in all it seems that the libMPcodecs API does not like buffering
  and skipping frames that much, it is not designed to provide such
  things, is it?
  For example a return code of 0 or -1 to indicate that a frames
  was skipped. (At least this information is not passed to mencoder)

- Is a filter really the right way? Maybe it is better to provide this
  functionality directly by mencoder/mplayer.

I'm not so intimate with the API details so it is likely that there are
more elegant solutions.

Despite all the problems and hacks the filter works here quite well and
produces the correct result even if there is a change in the pattern of
duplicated frames (counted as 'resyncs').

So, what so you think?
Bye,
	Johannes
-- 
http://beam.to/Hawk
PGP Key fingerprint = 2F 72 52 1C CF 86 96 35  8A DD 8E F5 C6 05 CE C5
-------------- next part --------------
diff -Naur main/libmpcodecs/Makefile main.dev/libmpcodecs/Makefile
--- main/libmpcodecs/Makefile	2003-02-11 22:44:55.000000000 +0100
+++ main.dev/libmpcodecs/Makefile	2003-02-15 20:12:24.000000000 +0100
@@ -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_xanim.c vd_xvid.c vd_libdv.c vd_qtvideo.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_pp.c vf_scale.c vf_format.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
+VFILTER_SRCS=vf.c vf_vo.c vf_crop.c vf_expand.c vf_pp.c vf_scale.c vf_format.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_ivtc.c
 ENCODER_SRCS=ve.c ve_divx4.c ve_lavc.c ve_vfw.c ve_rawrgb.c ve_libdv.c ve_xvid.c ve_qtvideo.c
 
 NATIVE_SRCS=native/RTjpegN.c native/cinepak.c native/fli.c native/minilzo.c native/msvidc.c native/nuppelvideo.c native/qtrle.c native/qtrpza.c native/qtsmc.c native/roqav.c native/xa_gsm.c native/svq1.c
diff -Naur main/libmpcodecs/mp_image.h main.dev/libmpcodecs/mp_image.h
--- main/libmpcodecs/mp_image.h	2003-01-30 10:14:58.000000000 +0100
+++ main.dev/libmpcodecs/mp_image.h	2003-02-15 20:12:25.000000000 +0100
@@ -61,6 +61,8 @@
 #define MP_IMGTYPE_IP 3
 // I+P+B type, requires 2+ independent static R/W and 1+ temp WO buffers
 #define MP_IMGTYPE_IPB 4
+// Buffer, requires 4+ independent static R/W and 1+ temp WO buffers
+#define MP_IMGTYPE_BUF 5
 
 #define MP_MAX_PLANES	4
 
diff -Naur main/libmpcodecs/vf.c main.dev/libmpcodecs/vf.c
--- main/libmpcodecs/vf.c	2003-02-11 22:44:55.000000000 +0100
+++ main.dev/libmpcodecs/vf.c	2003-02-15 20:12:25.000000000 +0100
@@ -55,6 +55,7 @@
 extern vf_info_t vf_info_down3dright;
 extern vf_info_t vf_info_field;
 extern vf_info_t vf_info_denoise3d;
+extern vf_info_t vf_info_ivtc;
 
 char** vo_plugin_args=(char**) NULL;
 
@@ -103,6 +104,7 @@
     &vf_info_down3dright,
     &vf_info_field,
     &vf_info_denoise3d,
+    &vf_info_ivtc,
     NULL
 };
 
@@ -181,6 +183,13 @@
     mpi=vf->imgctx.static_images[vf->imgctx.static_idx];
     vf->imgctx.static_idx^=1;
     break;
+  case MP_IMGTYPE_BUF:
+    if (!vf->imgctx.static_images[vf->imgctx.static_idx])
+     vf->imgctx.static_images[vf->imgctx.static_idx] =
+       new_mp_image (w2, h);
+    mpi = vf->imgctx.static_images[vf->imgctx.static_idx];
+    vf->imgctx.static_idx = ++vf->imgctx.static_idx % BUF_SZ;
+    break;
   }
   if(mpi){
     mpi->type=mp_imgtype;
diff -Naur main/libmpcodecs/vf.h main.dev/libmpcodecs/vf.h
--- main/libmpcodecs/vf.h	2002-12-05 01:22:37.000000000 +0100
+++ main.dev/libmpcodecs/vf.h	2003-02-15 20:12:25.000000000 +0100
@@ -10,8 +10,10 @@
     int (*open)(struct vf_instance_s* vf,char* args);
 } vf_info_t;
 
+#define BUF_SZ 6
+
 typedef struct vf_image_context_s {
-    mp_image_t* static_images[2];
+    mp_image_t* static_images[BUF_SZ];
     mp_image_t* temp_images[1];
     mp_image_t* export_images[1];
     int static_idx;
diff -Naur main/libmpcodecs/vf_ivtc.c main.dev/libmpcodecs/vf_ivtc.c
--- main/libmpcodecs/vf_ivtc.c	1970-01-01 01:00:00.000000000 +0100
+++ main.dev/libmpcodecs/vf_ivtc.c	2003-02-16 03:24:48.000000000 +0100
@@ -0,0 +1,208 @@
+/* 
+    IVTC filter for mplayer/mencoder by Johannes Guenther <hawkone at bigfoot.com>
+
+    Some ideas came from Donald Graft's decimate filter for VirtualDub
+    (http://sauron.mordor.net/dgraft/decimate.html)
+
+    Every 5 frames IVTC drops one frame. It tries to drop duplicates (generated
+    by a telecine process), otherwise a warning is outputed.
+
+    usage: 
+      -vop ivtc[=[v][frames_hint]]
+      v           - be verbose
+      frames_hint - #frames of movie (maybe #frames-2), used to flush the last
+                    frames out of buffer, otherwise you will loose up to 6
+                    frames from the end of the movie
+      you may also want to specify '-noskip', '-mc 0' and '-ofps'
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include "../config.h"
+
+#include "mp_image.h"
+#include "vf.h"
+
+#include "../libvo/fastmemcpy.h"
+
+
+#if BUF_SZ < 6
+#error "Buffer to small"
+#endif
+
+#define fno vf->priv->frame_num
+#define ofno ((vf->priv->frame_num-5)%BUF_SZ)
+#define verb vf->priv->verbose
+#define skip vf->priv->skipf
+#define buf vf->priv->buffer
+
+struct vf_priv_s {
+  int frame_num;
+  int frames;
+  int last_skipno;
+  int asyncs;
+  int nkeep;
+  int nskip;
+  int verbose;
+  int skipf[BUF_SZ];
+  mp_image_t *buffer[BUF_SZ];
+};
+
+//===========================================================================//
+static int config(vf_instance_t * vf, int width, int height, int d_width, int d_height, unsigned int flags,
+                  unsigned int outfmt)
+{
+  return vf_next_config(vf, width, height, d_width, d_height, flags, outfmt);
+}
+
+static int put_image(vf_instance_t * vf, mp_image_t * mpi)
+{
+  int i, j, min, lmin, diff, skipno, steps;
+  mp_image_t *dmpi;
+
+  dmpi = buf[++fno % BUF_SZ] = vf_get_image(vf->next, mpi->imgfmt, MP_IMGTYPE_BUF, 0, mpi->w, mpi->h);
+  skip[fno % BUF_SZ] = 0;
+
+  memcpy(dmpi->planes[0], mpi->planes[0], dmpi->stride[0] * dmpi->h);
+  memcpy(dmpi->planes[1], mpi->planes[1], dmpi->stride[1] * dmpi->h >> dmpi->chroma_y_shift);
+  memcpy(dmpi->planes[2], mpi->planes[2], dmpi->stride[2] * dmpi->h >> dmpi->chroma_y_shift);
+/*  dmpi->planes[0] = mpi->planes[0];
+  dmpi->planes[1] = mpi->planes[1];
+  dmpi->planes[2] = mpi->planes[2];
+  dmpi->stride[0] = mpi->stride[0];
+  dmpi->stride[1] = mpi->stride[1];
+  dmpi->stride[2] = mpi->stride[2];
+  dmpi->width = mpi->width;
+  dmpi->height = mpi->height;*/
+
+
+  if (fno < 5)                                                  // skip first 5 frames
+    return -1;
+//    return 0;
+
+  if (!(fno % 5)) {                                             // start with comparing immediately after we have enough (=6) frames in buffer
+    steps = 128;                                                // skip 128 pixels for speed reasons (for sure 1-3 cache lines skipped).
+    if (verb)
+      puts("");
+    do {
+      lmin = min = INT_MAX;
+      for (j = 0; j < 5; j++) {                                 // calculate differences between consecutive frames
+        diff = 0;
+        for (i = 0; i < dmpi->stride[0] * dmpi->h; i += steps)  // only every 'steps' pixel is considered
+          diff += abs(buf[(fno - 5 + j) % BUF_SZ]->planes[0][i] - buf[(fno - 4 + j) % BUF_SZ]->planes[0][i]);
+        if (verb)
+          printf("  cmp %i-%i => diff %i\n", fno - 5 + j, fno - 4 + j, diff);
+        if (diff < min) {                                       // remember minimum difference
+          min = diff;
+          skipno = fno - 5 + j;
+        }
+        if ((diff < lmin) && (diff > min))                      // and also the 'almost minimum'
+          lmin = diff;
+      }
+      if ((min * 100) / lmin < 40)                              // clear minimum found (difference 40% lower than next difference in question)
+        steps = -1;
+      else if (steps == 128) {
+        steps = 16;                                             // no clear minimum, search again with more accuracy
+        if (verb) {
+          puts(" Hmm, I'm not sure - trying again");
+        }
+      } else
+        steps = 1;
+    } while (steps > 15);
+
+    if (skipno - vf->priv->last_skipno != 5) {                  // break in frame sequence pattern?
+      vf->priv->asyncs++;
+      if (steps == 1)
+        printf("Warning: possibly skipped wrong frame (%i) | %i to %i = %i%%.\n", skipno, min, lmin,
+               (min * 100) / lmin);
+    }
+    vf->priv->last_skipno = skipno;
+    skip[skipno % BUF_SZ] = 1;                                  // mark this frame to be skipped
+    if (verb)
+      printf("=> let's skip frame no %i (%i) (diff was %i)\n", skipno, skipno % BUF_SZ, min);
+  }
+
+  if (fno == vf->priv->frames) {                                // this is the last time we are called -> flush buffer
+    if (verb)
+      puts("!! reached last frame - flushing buffer");
+    for (i = 0; i < 5; i++) {
+      if (verb)
+        if (skip[ofno]) {
+          printf("%i skipping frame no %i (%i)\n", i, fno - 5, ofno);
+          vf->priv->nskip++;
+        } else {
+          printf("%i keeping frame no %i (%i)\n", i, fno - 5, ofno);
+          vf_next_put_image(vf, buf[ofno]);
+          vf->priv->nkeep++;
+        }
+      fno++;
+    }
+  }
+
+  if (skip[ofno]) {
+    if (verb)
+      printf("skipping frame no %i (%i)\n", fno - 5, ofno);
+    vf->priv->nskip++;
+    return -1;
+//    return 0;
+  } else {
+    if (verb)
+      printf("keeping frame no %i (%i)\n", fno - 5, ofno);
+    vf->priv->nkeep++;
+    return vf_next_put_image(vf, buf[ofno]);
+  }
+}
+
+//===========================================================================//
+
+static void uninit(struct vf_instance_s *vf)
+{
+  if (!vf->priv)
+    return;
+
+  printf("[IVTC] uninit. frames processed: %i (%i delivered, %i skipped)), resyncs: %i\n", fno - 5, vf->priv->nkeep,
+         vf->priv->nskip, vf->priv->asyncs);
+
+  free(vf->priv);
+  vf->priv = NULL;
+}
+
+//===========================================================================//
+
+static int open(vf_instance_t * vf, char *args)
+{
+  vf->config = config;
+  vf->put_image = put_image;
+  vf->uninit = uninit;
+  vf->default_reqs = VFCAP_ACCEPT_STRIDE;
+  vf->priv = malloc(sizeof(struct vf_priv_s));
+  vf->priv->frame_num = -1;
+  vf->priv->frames = -1;
+  vf->priv->last_skipno = 0;
+  vf->priv->asyncs = -1;
+  vf->priv->nkeep = -1;
+  vf->priv->nskip = 0;
+  vf->priv->verbose = 0;
+  if (args) {
+    if (args[0] == 'v') {
+      vf->priv->verbose = 1;
+      args++;
+    }
+    vf->priv->frames = atoi(args);
+  }
+  printf("[IVTC] %sverbose, frames: %i\n", vf->priv->verbose ? "" : "not ", vf->priv->frames);
+  return 1;
+}
+
+vf_info_t vf_info_ivtc = {
+  "InVerse TeleCine",
+  "ivtc",
+  "Hawk",
+  "usage: -vop ivtc[=[v][frames_hint]]",
+  open
+};
+
+//===========================================================================//


More information about the MPlayer-dev-eng mailing list