[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