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

Himangi Saraogi himangi774 at gmail.com
Tue Mar 10 22:50:47 CET 2015


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

The algorithm was briefly discussed with Carl. The algorithm is not completely
tested, though I do have a some sample suggestions and will be testing on
them soon. Documentation is yet to be added.
---
 Changelog                   |   1 +
 libavfilter/Makefile        |   1 +
 libavfilter/allfilters.c    |   1 +
 libavfilter/vf_detelecine.c | 323 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 326 insertions(+)
 create mode 100644 libavfilter/vf_detelecine.c

diff --git a/Changelog b/Changelog
index e88359d..341faca 100644
--- a/Changelog
+++ b/Changelog
@@ -3,6 +3,7 @@ releases are sorted from youngest to oldest.
 
 version <next>:
 - FFT video filter
+- Detelecine filter
 
 
 version 2.6:
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index b184f07..399072c 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 043ac56..2e4e2f6 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..ce9ba74
--- /dev/null
+++ b/libavfilter/vf_detelecine.c
@@ -0,0 +1,323 @@
+/*
+ * 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;
+    unsigned int pattern_pos;
+    unsigned int nskip_fields;
+
+    AVRational pts;
+    double ts_unit;
+    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},
+    {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;
+
+    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 i, 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);
+
+    s->ts_unit = av_q2d(av_inv_q(av_mul_q(fps, outlink->time_base)));
+
+    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 * s->ts_unit;
+        ret = ff_filter_frame(outlink, frame);
+    }
+
+    av_frame_free(&inpicref);
+
+    return ret;
+}
+
+static av_cold void uninit(AVFilterContext *ctx)
+{
+    DetelecineContext *s = ctx->priv;
+    int i;
+
+    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