[FFmpeg-devel] [PATCH] lavfi: add inverse telecine filter

Himangi Saraogi himangi774 at gmail.com
Sun Mar 22 23:13:55 CET 2015


This is an exact inverse of the telecine filter unlike previously existing
pullup and fieldmatch ones.

Tested on few samples generated using the telecine filter. Documentation is
yet to be added. Added an additional parameter "start_frame" to allow using
the filter for a stream that was cut and improved pts handling.

---
 Changelog                   |   2 +-
 libavfilter/Makefile        |   1 +
 libavfilter/allfilters.c    |   1 +
 libavfilter/vf_detelecine.c | 335 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 338 insertions(+), 1 deletion(-)
 create mode 100644 libavfilter/vf_detelecine.c

diff --git a/Changelog b/Changelog
index 4950330..8733203 100644
--- a/Changelog
+++ b/Changelog
@@ -7,7 +7,7 @@ version <next>:
 - DTS lossless extension (XLL) decoding (not lossless, disabled by default)
 - showwavespic filter
 - libdcadec wrapper
-
+- Detelecine filter
 
 version 2.6:
 - nvenc encoder
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 2cde029..73e7adf 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -112,6 +112,7 @@ OBJS-$(CONFIG_DECIMATE_FILTER)               += vf_decimate.o
 OBJS-$(CONFIG_DEJUDDER_FILTER)               += vf_dejudder.o
 OBJS-$(CONFIG_DELOGO_FILTER)                 += vf_delogo.o
 OBJS-$(CONFIG_DESHAKE_FILTER)                += vf_deshake.o
+OBJS-$(CONFIG_DETELECINE_FILTER)             += vf_detelecine.o
 OBJS-$(CONFIG_DRAWBOX_FILTER)                += vf_drawbox.o
 OBJS-$(CONFIG_DRAWGRID_FILTER)               += vf_drawbox.o
 OBJS-$(CONFIG_DRAWTEXT_FILTER)               += vf_drawtext.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 0288082..6bc01c5 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -128,6 +128,7 @@ void avfilter_register_all(void)
     REGISTER_FILTER(DEJUDDER,       dejudder,       vf);
     REGISTER_FILTER(DELOGO,         delogo,         vf);
     REGISTER_FILTER(DESHAKE,        deshake,        vf);
+    REGISTER_FILTER(DETELECINE,     detelecine,     vf);
     REGISTER_FILTER(DRAWBOX,        drawbox,        vf);
     REGISTER_FILTER(DRAWGRID,       drawgrid,       vf);
     REGISTER_FILTER(DRAWTEXT,       drawtext,       vf);
diff --git a/libavfilter/vf_detelecine.c b/libavfilter/vf_detelecine.c
new file mode 100644
index 0000000..36d4e94
--- /dev/null
+++ b/libavfilter/vf_detelecine.c
@@ -0,0 +1,335 @@
+/*
+ * Copyright (c) 2015 Himangi Saraogi <himangi774 at gmail.com>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file detelecine filter.
+ */
+
+
+#include "libavutil/avstring.h"
+#include "libavutil/imgutils.h"
+#include "libavutil/opt.h"
+#include "libavutil/pixdesc.h"
+#include "avfilter.h"
+#include "formats.h"
+#include "internal.h"
+#include "video.h"
+
+typedef struct {
+    const AVClass *class;
+    int first_field;
+    char *pattern;
+    int start_frame;
+    unsigned int pattern_pos;
+    unsigned int nskip_fields;
+
+    AVRational pts;
+    int occupied;
+
+    int nb_planes;
+    int planeheight[4];
+    int stride[4];
+
+    AVFrame *frame;
+    AVFrame *temp;
+} DetelecineContext;
+
+#define OFFSET(x) offsetof(DetelecineContext, x)
+#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
+
+static const AVOption detelecine_options[] = {
+    {"first_field", "select first field", OFFSET(first_field), AV_OPT_TYPE_INT,   {.i64=0}, 0, 1, FLAGS, "field"},
+    {"top",    "select top field first",                0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, FLAGS, "field"},
+    {"t",      "select top field first",                0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, FLAGS, "field"},
+    {"bottom", "select bottom field first",             0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, FLAGS, "field"},
+    {"b",      "select bottom field first",             0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, FLAGS, "field"},
+    {"pattern", "pattern that describe for how many fields a frame is to be displayed", OFFSET(pattern), AV_OPT_TYPE_STRING, {.str="23"}, 0, 0, FLAGS},
+    {"start_frame", "position of first frame with respect to the pattern if stream is cut", OFFSET(start_frame), AV_OPT_TYPE_INT, {.i64=0}, 0, 13, FLAGS},
+    {NULL}
+};
+
+AVFILTER_DEFINE_CLASS(detelecine);
+
+static av_cold int init(AVFilterContext *ctx)
+{
+    DetelecineContext *s = ctx->priv;
+    const char *p;
+    int max = 0;
+
+    if (!strlen(s->pattern)) {
+        av_log(ctx, AV_LOG_ERROR, "No pattern provided.\n");
+        return AVERROR_INVALIDDATA;
+    }
+
+    for (p = s->pattern; *p; p++) {
+        if (!av_isdigit(*p)) {
+            av_log(ctx, AV_LOG_ERROR, "Provided pattern includes non-numeric characters.\n");
+            return AVERROR_INVALIDDATA;
+        }
+
+        max = FFMAX(*p - '0', max);
+        s->pts.num += *p - '0';
+        s->pts.den += 2;
+    }
+
+    s->nskip_fields = 0;
+    s->pattern_pos = 0;
+
+    if (s->start_frame != 0) {
+        int nfields = 0;
+        for (p = s->pattern; *p; p++) {
+            nfields += *p - '0';
+            s->pattern_pos++;
+            if (nfields >= 2*s->start_frame) {
+                s->nskip_fields = nfields - 2*s->start_frame;
+                break;
+            }
+        }
+    }
+
+    av_log(ctx, AV_LOG_INFO, "Detelecine pattern %s removes up to %d frames per frame, pts advance factor: %d/%d\n",
+           s->pattern, (max + 1) / 2, s->pts.num, s->pts.den);
+
+    return 0;
+}
+
+static int query_formats(AVFilterContext *ctx)
+{
+    AVFilterFormats *pix_fmts = NULL;
+    int fmt;
+
+    for (fmt = 0; av_pix_fmt_desc_get(fmt); fmt++) {
+        const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt);
+        if (!(desc->flags & AV_PIX_FMT_FLAG_HWACCEL ||
+              desc->flags & AV_PIX_FMT_FLAG_PAL     ||
+              desc->flags & AV_PIX_FMT_FLAG_BITSTREAM))
+            ff_add_format(&pix_fmts, fmt);
+    }
+
+    ff_set_common_formats(ctx, pix_fmts);
+    return 0;
+}
+
+static int config_input(AVFilterLink *inlink)
+{
+    DetelecineContext *s = inlink->dst->priv;
+    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
+    int ret;
+
+    s->temp = ff_get_video_buffer(inlink, inlink->w, inlink->h);
+    if (!s->temp)
+        return AVERROR(ENOMEM);
+
+    s->frame = ff_get_video_buffer(inlink, inlink->w, inlink->h);
+    if (!s->frame)
+        return AVERROR(ENOMEM);
+
+    if ((ret = av_image_fill_linesizes(s->stride, inlink->format, inlink->w)) < 0)
+        return ret;
+
+    s->planeheight[1] = s->planeheight[2] = FF_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h);
+    s->planeheight[0] = s->planeheight[3] = inlink->h;
+
+    s->nb_planes = av_pix_fmt_count_planes(inlink->format);
+
+    return 0;
+}
+
+static int config_output(AVFilterLink *outlink)
+{
+    AVFilterContext *ctx = outlink->src;
+    DetelecineContext *s = ctx->priv;
+    const AVFilterLink *inlink = ctx->inputs[0];
+    AVRational fps = inlink->frame_rate;
+
+    if (!fps.num || !fps.den) {
+        av_log(ctx, AV_LOG_ERROR, "The input needs a constant frame rate; "
+               "current rate of %d/%d is invalid\n", fps.num, fps.den);
+        return AVERROR(EINVAL);
+    }
+    fps = av_mul_q(fps, av_inv_q(s->pts));
+    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->frame_rate = fps;
+    outlink->time_base = av_mul_q(inlink->time_base, s->pts);
+    av_log(ctx, AV_LOG_VERBOSE, "TB: %d/%d -> %d/%d\n",
+           inlink->time_base.num, inlink->time_base.den, outlink->time_base.num, outlink->time_base.den);
+
+    return 0;
+}
+
+static int filter_frame(AVFilterLink *inlink, AVFrame *inpicref)
+{
+    AVFilterContext *ctx = inlink->dst;
+    AVFilterLink *outlink = ctx->outputs[0];
+    DetelecineContext *s = ctx->priv;
+    int i, len = 0, ret = 0, out = 0;
+
+    if (s->nskip_fields >= 2) {
+        s->nskip_fields -= 2;
+        return 0;
+    } else if (s->nskip_fields >= 1) {
+        if (s->occupied) {
+            s->occupied = 0;
+            s->nskip_fields--;
+        }
+        else {
+            for (i = 0; i < s->nb_planes; i++) {
+                av_image_copy_plane(s->temp->data[i], s->temp->linesize[i],
+                                    inpicref->data[i], inpicref->linesize[i],
+                                    s->stride[i],
+                                    s->planeheight[i]);
+            }
+            s->occupied = 1;
+            s->nskip_fields--;
+            return 0;
+        }
+    }
+
+    if (s->nskip_fields == 0) {
+        while(!len && s->pattern[s->pattern_pos]) {
+            len = s->pattern[s->pattern_pos] - '0';
+            s->pattern_pos++;
+        }
+
+        if (!s->pattern[s->pattern_pos])
+            s->pattern_pos = 0;
+
+        if(!len) { // do not output any field as the entire pattern is zero
+            av_frame_free(&inpicref);
+            return 0;
+        }
+
+        if (s->occupied) {
+            for (i = 0; i < s->nb_planes; i++) {
+                // fill in the EARLIER field from the new pic
+                av_image_copy_plane(s->frame->data[i] + s->frame->linesize[i] * s->first_field,
+                                    s->frame->linesize[i] * 2,
+                                    inpicref->data[i] + inpicref->linesize[i] * s->first_field,
+                                    inpicref->linesize[i] * 2,
+                                    s->stride[i],
+                                    (s->planeheight[i] - s->first_field + 1) / 2);
+                // fill in the LATER field from the buffered pic
+                av_image_copy_plane(s->frame->data[i] + s->frame->linesize[i] * !s->first_field,
+                                    s->frame->linesize[i] * 2,
+                                    s->temp->data[i] + s->temp->linesize[i] * !s->first_field,
+                                    s->temp->linesize[i] * 2,
+                                    s->stride[i],
+                                    (s->planeheight[i] - !s->first_field + 1) / 2);
+            }
+            len -= 2;
+            for (i = 0; i < s->nb_planes; i++) {
+                av_image_copy_plane(s->temp->data[i], s->temp->linesize[i],
+                                    inpicref->data[i], inpicref->linesize[i],
+                                    s->stride[i],
+                                    s->planeheight[i]);
+            }
+            s->occupied = 1;
+            out = 1;
+        } else {
+            if (len >= 2) {
+                // output THIS image as-is
+                for (i = 0; i < s->nb_planes; i++)
+                    av_image_copy_plane(s->frame->data[i], s->frame->linesize[i],
+                                        inpicref->data[i], inpicref->linesize[i],
+                                        s->stride[i],
+                                        s->planeheight[i]);
+                len -= 2;
+                out = 1;
+            } else if (len == 1) {
+                // fill in the EARLIER field from the new pic
+                av_image_copy_plane(s->frame->data[i] + s->frame->linesize[i] * s->first_field,
+                                    s->frame->linesize[i] * 2,
+                                    inpicref->data[i] + inpicref->linesize[i] * s->first_field,
+                                    inpicref->linesize[i] * 2,
+                                    s->stride[i],
+                                    (s->planeheight[i] - s->first_field + 1) / 2);
+                // TODO: not sure about the other field
+
+                len--;
+                out = 1;
+            }
+        }
+
+        if (len == 1 && s->occupied)
+        {
+            len--;
+            s->occupied = 0;
+        }
+    }
+    s->nskip_fields = len;
+
+    if (out) {
+        AVFrame *frame = av_frame_clone(s->frame);
+
+        if (!frame) {
+            av_frame_free(&inpicref);
+            return AVERROR(ENOMEM);
+        }
+
+        frame->pts = outlink->frame_count * av_q2d(av_inv_q(av_mul_q(outlink->frame_rate, outlink->time_base)));
+        ret = ff_filter_frame(outlink, frame);
+    }
+
+    av_frame_free(&inpicref);
+
+    return ret;
+}
+
+static av_cold void uninit(AVFilterContext *ctx)
+{
+    DetelecineContext *s = ctx->priv;
+
+    av_frame_free(&s->temp);
+    av_frame_free(&s->frame);
+}
+
+static const AVFilterPad detelecine_inputs[] = {
+    {
+        .name          = "default",
+        .type          = AVMEDIA_TYPE_VIDEO,
+        .filter_frame  = filter_frame,
+        .config_props  = config_input,
+    },
+    { NULL }
+};
+
+static const AVFilterPad detelecine_outputs[] = {
+    {
+        .name          = "default",
+        .type          = AVMEDIA_TYPE_VIDEO,
+        .config_props  = config_output,
+    },
+    { NULL }
+};
+
+AVFilter ff_vf_detelecine = {
+    .name          = "detelecine",
+    .description   = NULL_IF_CONFIG_SMALL("Apply an inverse telecine pattern."),
+    .priv_size     = sizeof(DetelecineContext),
+    .priv_class    = &detelecine_class,
+    .init          = init,
+    .uninit        = uninit,
+    .query_formats = query_formats,
+    .inputs        = detelecine_inputs,
+    .outputs       = detelecine_outputs,
+};
-- 
1.9.1



More information about the ffmpeg-devel mailing list