[FFmpeg-devel] Proposed vf_decimate enhancement

Ray Cole the.other.ray.cole at gmail.com
Tue Sep 29 18:02:33 CEST 2015


Here is an updated patch. I cleaned the code up to hopefully be closer to standards. It works well for me, but your mileage may vary...

-------------- next part --------------
--- vf_decimate.c	2015-09-29 10:56:46.171698492 -0500
+++ vf_decimatex.c	2015-09-29 10:59:50.679695685 -0500
@@ -27,6 +27,7 @@
 
 #define INPUT_MAIN     0
 #define INPUT_CLEANSRC 1
+#define DROP_HISTORY   8
 
 struct qitem {
     AVFrame *frame;
@@ -51,6 +52,10 @@
     int bdiffsize;
     int64_t *bdiffs;
 
+    /* Ray */
+    int lastdrop;
+    int64_t drop_count[25]; // drop counts
+
     /* options */
     int cycle;
     double dupthresh_flt;
@@ -60,6 +65,9 @@
     int blockx, blocky;
     int ppsrc;
     int chroma;
+    int force_drop;
+    int lock_on;
+
 } DecimateContext;
 
 #define OFFSET(x) offsetof(DecimateContext, x)
@@ -71,9 +79,13 @@
     { "scthresh",  "set scene change threshold", OFFSET(scthresh_flt),  AV_OPT_TYPE_DOUBLE, {.dbl = 15.0}, 0, 100, FLAGS },
     { "blockx",    "set the size of the x-axis blocks used during metric calculations", OFFSET(blockx), AV_OPT_TYPE_INT, {.i64 = 32}, 4, 1<<9, FLAGS },
     { "blocky",    "set the size of the y-axis blocks used during metric calculations", OFFSET(blocky), AV_OPT_TYPE_INT, {.i64 = 32}, 4, 1<<9, FLAGS },
-    { "ppsrc",     "mark main input as a pre-processed input and activate clean source input stream", OFFSET(ppsrc), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS },
-    { "chroma",    "set whether or not chroma is considered in the metric calculations", OFFSET(chroma), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1, FLAGS },
+    { "ppsrc",     "mark main input as a pre-processed input and activate clean source input stream", OFFSET(ppsrc), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, FLAGS },
+    { "chroma",    "set whether or not chroma is considered in the metric calculations", OFFSET(chroma), AV_OPT_TYPE_INT, {.i64=1}, 0, 1, FLAGS },
+    { "force_drop",    "set to forcefully drop frame X in cycle", OFFSET(force_drop), AV_OPT_TYPE_INT, {.i64=-1}, -1, 4, FLAGS },
+    { "lock_on",    "set to lock on to a cycle", OFFSET(lock_on), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, FLAGS },
+
     { NULL }
+
 };
 
 AVFILTER_DEFINE_CLASS(decimate);
@@ -140,13 +152,15 @@
     q->totdiff = 0;
     for (i = 0; i < dm->bdiffsize; i++)
         q->totdiff += bdiffs[i];
+
     q->maxbdiff = maxdiff;
+
 }
 
 static int filter_frame(AVFilterLink *inlink, AVFrame *in)
 {
-    int scpos = -1, duppos = -1;
-    int drop = INT_MIN, i, lowest = 0, ret;
+    int scpos = -1, duppos = -1, common = 0, start = 0;
+    int drop = INT_MIN, i, lowest = 0, lowest_tot = 0, ret =0;
     AVFilterContext *ctx  = inlink->dst;
     AVFilterLink *outlink = ctx->outputs[0];
     DecimateContext *dm   = ctx->priv;
@@ -176,17 +190,128 @@
         dm->last = av_frame_clone(in);
         dm->fid = 0;
 
+
+// The major change starts here
+
+// First we will NOT consider the 'next' frame in the drop detection because that would be wrong.
+// The old code would allow frame 0 to be dropped immediately after frame 4. I've not seen a case where that makes sense and
+// frame 0 could never be followed by a drop of 1, nor could frame 1 be followed by 2, etc. because of the way detection is
+// performed 5 frames at a time. So first we start at what _should_ be a reasonable point to be closer inline with what can
+// happen when frames 0, 1 and 2 are the drops.
+
+        start = 0;
+
+        if (dm->lastdrop == (dm->cycle - 1))  
+           start = 2;                         
+        else
+        if (dm->lastdrop == (dm->cycle - 2))  
+           start = 1;
+        
         /* we have a complete cycle, select the frame to drop */
-        lowest = 0;
+        lowest = start;              
+        lowest_tot = start;
+
+// We will now locate the lowest frame by diff and by total. 
+
         for (i = 0; i < dm->cycle; i++) {
             if (dm->queue[i].totdiff > dm->scthresh)
                 scpos = i;
-            if (dm->queue[i].maxbdiff < dm->queue[lowest].maxbdiff)
-                lowest = i;
+
+            if (i >= start || scpos >= 0) {    // if in range of eligible for pattern drop
+                if (dm->queue[lowest].maxbdiff == 0 ||
+                    dm->queue[i].maxbdiff < dm->queue[lowest].maxbdiff)
+
+                    lowest = i;
+
+                if (dm->queue[lowest_tot].totdiff == 0 ||
+                    dm->queue[i].totdiff < dm->queue[lowest_tot].totdiff)
+
+                    lowest_tot = i;
+            }
+
         }
+
         if (dm->queue[lowest].maxbdiff < dm->dupthresh)
             duppos = lowest;
-        drop = scpos >= 0 && duppos < 0 ? scpos : lowest;
+
+// If the lowest from by max and total do not agree, and this is not the first cycle,
+// then we need to figure out how to resolve the conflict.
+// To resolve it we first find out the most commonly dropped frame that we have been
+// seeing recently. If either of the two 'lowest' values match the commonly-dropped
+// frame then we assume the cycle continues - majority rules. If the lowests and 
+// common do not agree then we assume the last frame we dropped is probably OK...
+
+        if (lowest_tot != lowest &&    // the two methods disagree
+            dm->lastdrop >= 0) {       // and this isn't our first drop
+
+            common = 0;
+
+            for (i=0;i < dm->cycle; i ++) {
+                if (dm->drop_count[i] > dm->drop_count[common])
+                    common = i;
+            }
+
+            if (common == lowest)     // if most common match lowest
+                lowest = common;      // use common
+            else                      // otherwise
+            if (common == lowest_tot) // if most common matches lowest by total
+                lowest = common;      // use common
+            else                      // otherwise
+                lowest = dm->lastdrop;    
+        };
+    
+// Scene change detection seemed difficult to tune so this code ignores scene changes entirely.
+// The default duplicate detection didn't work for me and I never could find a value that was good.
+// If scene detection is enabled I really think it should gather some statistics from early frames
+// and compute what is likely a good duplicate threshold for duplicates. Frankly I have yet to come
+// across anything where scene change detection bought me anything...
+
+//       drop = scpos >= 0 && duppos < 0 ? scpos : lowest;
+        drop = lowest;    
+
+        dm->drop_count[drop] ++;
+
+        if (dm->drop_count[drop] > DROP_HISTORY) {    // if reached a "stable" threshold
+
+// This is experimental. If you know the cycle NEVER changes then this
+// will lock onto the cycle permanently. Limited use.
+
+            if (dm->lock_on == 1 && dm->force_drop < 0) {
+               av_log(ctx, AV_LOG_INFO, "Locking onto pattern - will forcefully drop %d.\n", drop);
+               dm->force_drop = drop;
+            }
+            else {
+                 if (dm->force_drop >= 0 && drop != dm->force_drop) {
+                     av_log(ctx, AV_LOG_ERROR, "Forced pattern may not be working...ending.\n");
+                     return -4;
+                 }
+            }
+
+// Reset the drop counts, but keep the dominate drop 'high'.
+
+            memset(&dm->drop_count, 0, sizeof(dm->drop_count));  // reset cycles
+            dm->drop_count[drop] = DROP_HISTORY / 2;             // keep this one 'high' for now
+        }
+
+// If we are forcing a drop then make it force but note the difference. Forcing a drop is
+// only useful if you know the cycle. I had such a clip that I used to test the other algorithms
+// and this allowed me to note areas where an incorrect frame was dropped. With the changes in
+// determining the drop based on both tot and max diffs these 'errors' tended to happen only in
+// blackout scenes or stills - harmless errors.
+        if (dm->force_drop >= 0 && drop != dm->force_drop) { // if forcing drop
+            av_log(ctx, AV_LOG_DEBUG,
+                   "Wanted to drop %d, but forcefully dropping %d.\n",
+                   drop, dm->force_drop);    
+            drop = dm->force_drop;
+        }
+
+// A change in cycle has been detected. Note it for diagnostic purposes. Probably shouldn't be
+// an 'INFO' but made it easier to debug.
+
+        if (drop != dm->lastdrop)
+            av_log(ctx, AV_LOG_DEBUG, "Cycle change. Pulldown dropping %d\n", drop);
+
+        dm->lastdrop = drop;
     }
 
     /* metrics debug */
@@ -203,7 +328,7 @@
     }
 
     /* push all frames except the drop */
-    ret = 0;
+
     for (i = 0; i < dm->cycle && dm->queue[i].frame; i++) {
         if (i == drop) {
             if (dm->ppsrc)
@@ -239,7 +364,7 @@
 
     dm->hsub      = pix_desc->log2_chroma_w;
     dm->vsub      = pix_desc->log2_chroma_h;
-    dm->depth     = pix_desc->comp[0].depth;
+    dm->depth     = pix_desc->comp[0].depth_minus1 + 1;
     max_value     = (1 << dm->depth) - 1;
     dm->scthresh  = (int64_t)(((int64_t)max_value *          w * h          * dm->scthresh_flt)  / 100);
     dm->dupthresh = (int64_t)(((int64_t)max_value * dm->blockx * dm->blocky * dm->dupthresh_flt) / 100);
@@ -248,6 +373,8 @@
     dm->bdiffsize = dm->nxblocks * dm->nyblocks;
     dm->bdiffs    = av_malloc_array(dm->bdiffsize, sizeof(*dm->bdiffs));
     dm->queue     = av_calloc(dm->cycle, sizeof(*dm->queue));
+    dm->lastdrop = -1;
+    memset(&dm->drop_count, 0, sizeof(dm->drop_count));
 
     if (!dm->bdiffs || !dm->queue)
         return AVERROR(ENOMEM);
@@ -372,6 +499,7 @@
     fps = av_mul_q(fps, (AVRational){dm->cycle - 1, dm->cycle});
     av_log(ctx, AV_LOG_VERBOSE, "FPS: %d/%d -> %d/%d\n",
            inlink->frame_rate.num, inlink->frame_rate.den, fps.num, fps.den);
+    outlink->flags |= FF_LINK_FLAG_REQUEST_LOOP;
     outlink->time_base  = inlink->time_base;
     outlink->frame_rate = fps;
     outlink->sample_aspect_ratio = inlink->sample_aspect_ratio;


More information about the ffmpeg-devel mailing list