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...
--- 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;
_______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel