--- vf_decimate.c Tue Sep 29 08:30:08 2015 +++ vf_decimatex.c Tue Sep 29 08:28:22 2015 @@ -51,6 +51,10 @@ int bdiffsize; int64_t *bdiffs; + /* Ray */ + int lastdrop; + int64_t drop_count[5]; // drop counts + /* options */ int cycle; double dupthresh_flt; @@ -60,6 +64,9 @@ int blockx, blocky; int ppsrc; int chroma; + int force_drop; + int lock_on; + } DecimateContext; #define OFFSET(x) offsetof(DecimateContext, x) @@ -71,9 +78,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 +151,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 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 +189,166 @@ 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. + + int iStart = 0; + + if (dm->lastdrop == (dm->cycle - 1)) // Do NOT allow it to drop 2 frames in a row...because that WOULD be wrong... + iStart = 2; // Last frame of cycle runs chance of consecutive frame drop without this... + // Perhaps should force 2 frames inbetween? Sure - let's start at 2 for 4, 1 for 3 + else + if (dm->lastdrop == (dm->cycle - 2)) // Do NOT allow it to drop 2 frames in a row...because that WOULD be wrong... + iStart = 1; // Make sure 2 frames skipped before next drop + /* we have a complete cycle, select the frame to drop */ - lowest = 0; + lowest = iStart; + lowest_tot = iStart; + +// 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 >= iStart || 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 + + { + + short idx = 1; + short iCommon = 0; + while (idx < dm->cycle) + { + + if (dm->drop_count[idx] > dm->drop_count[iCommon]) + iCommon = idx; + + idx ++; + + }; + + int origLowest = lowest; + if (iCommon == lowest) // if most common match lowest + lowest = iCommon; // use common + else // otherwise + if (iCommon == lowest_tot) // if most common matches lowest by total + lowest = iCommon; // use common + else // otherwise + lowest = dm->lastdrop; + +#ifdef _SRC_ + av_log(ctx, AV_LOG_INFO, "\n Max/Tot Disagree (%d %d). Using drop of %d\n", + origLowest, + lowest_tot, + lowest); +#endif + + }; + +// 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] > 8) // 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, "\nLocking 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, "\nForced 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] = 4; // 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_INFO, "\n 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_INFO, "\n Pulldown dropping %d\n", drop); + +#ifdef _SRC_ + av_log(ctx, AV_LOG_INFO, "MaxDiffs: %lld %lld %lld %lld %lld\n", + dm->queue[0].maxbdiff, + dm->queue[1].maxbdiff, + dm->queue[2].maxbdiff, + dm->queue[3].maxbdiff, + dm->queue[4].maxbdiff); + av_log(ctx, AV_LOG_INFO, "TotDiffs: %lld %lld %lld %lld %lld\n", + dm->queue[0].totdiff, + dm->queue[1].totdiff, + dm->queue[2].totdiff, + dm->queue[3].totdiff, + dm->queue[4].totdiff); +#endif + + }; + + dm->lastdrop = drop; } /* metrics debug */ @@ -203,7 +365,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 +401,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 +410,14 @@ 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; + dm->drop_count[0] = 0; + dm->drop_count[1] = 0; + dm->drop_count[2] = 0; + dm->drop_count[3] = 0; + dm->drop_count[4] = 0; + + av_log(ctx, AV_LOG_INFO, "Cycle: %d\n", dm->cycle); if (!dm->bdiffs || !dm->queue) return AVERROR(ENOMEM); @@ -372,6 +542,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;