lavfi: port tinterlace filter from MPlayer
authorStefano Sabatini <stefano.sabatini-lala@poste.it>
Sat, 25 Jun 2011 11:06:24 +0000 (13:06 +0200)
committerStefano Sabatini <stefasab@gmail.com>
Sat, 31 Dec 2011 10:43:10 +0000 (11:43 +0100)
Port MPlayer tinterlace filter from MPlayer, with some ideas taken
from the FFmbc/libavfilter port, with the following main differences:

* added support for full-scale YUVJ formats
* added support for YUVA420P
* request_frame() on the filter is forced to return a frame
* some code factorization (related to the copy_picture_fields() function)
* fixed black padding values for mode 3

Changelog
configure
doc/filters.texi
libavfilter/Makefile
libavfilter/allfilters.c
libavfilter/avfilter.h
libavfilter/vf_tinterlace.c [new file with mode: 0644]

index 4ab7882..c65ce07 100644 (file)
--- a/Changelog
+++ b/Changelog
@@ -11,6 +11,7 @@ version next:
 - thumbnail video filter
 - XML output in ffprobe
 - asplit audio filter
+- tinterlace video filter
 
 
 version 0.9:
index d4fb20e..6a19d60 100755 (executable)
--- a/configure
+++ b/configure
@@ -1652,6 +1652,7 @@ mptestsrc_filter_deps="gpl"
 negate_filter_deps="lut_filter"
 ocv_filter_deps="libopencv"
 scale_filter_deps="swscale"
+tinterlace_filter_deps="gpl"
 yadif_filter_deps="gpl"
 
 # libraries
index da36e8b..387a05e 100644 (file)
@@ -2433,6 +2433,40 @@ Complete example of a thumbnail creation with @command{ffmpeg}:
 ffmpeg -i in.avi -vf thumbnail,scale=300:200 -frames:v 1 out.png
 @end example
 
+@section tinterlace
+
+Perform various types of temporal field interlacing.
+
+Frames are counted starting from 1, so the first input frame is
+considered odd.
+
+This filter accepts a single parameter specifying the mode. Available
+modes are:
+
+@table @samp
+@item 0
+Move odd frames into the upper field, even into the lower field,
+generating a double height frame at half framerate.
+
+@item 1
+Only output even frames, odd frames are dropped, generating a frame with
+unchanged height at half framerate.
+
+@item 2
+Only output odd frames, even frames are dropped, generating a frame with
+unchanged height at half framerate.
+
+@item 3
+Expand each frame to full height, but pad alternate lines with black,
+generating a frame with double height at the same input framerate.
+
+@item 4
+Interleave the upper field from odd frames with the lower field from
+even frames, generating a frame with unchanged height at half framerate.
+@end table
+
+Default mode is 0.
+
 @section transpose
 
 Transpose rows with columns in the input video and optionally flip it.
index c8ce1d6..75f9b85 100644 (file)
@@ -81,6 +81,7 @@ OBJS-$(CONFIG_SHOWINFO_FILTER)               += vf_showinfo.o
 OBJS-$(CONFIG_SLICIFY_FILTER)                += vf_slicify.o
 OBJS-$(CONFIG_SPLIT_FILTER)                  += vf_split.o
 OBJS-$(CONFIG_THUMBNAIL_FILTER)              += vf_thumbnail.o
+OBJS-$(CONFIG_TINTERLACE_FILTER)             += vf_tinterlace.o
 OBJS-$(CONFIG_TRANSPOSE_FILTER)              += vf_transpose.o
 OBJS-$(CONFIG_UNSHARP_FILTER)                += vf_unsharp.o
 OBJS-$(CONFIG_VFLIP_FILTER)                  += vf_vflip.o
index adc78b1..1637be8 100644 (file)
@@ -91,6 +91,7 @@ void avfilter_register_all(void)
     REGISTER_FILTER (SLICIFY,     slicify,     vf);
     REGISTER_FILTER (SPLIT,       split,       vf);
     REGISTER_FILTER (THUMBNAIL,   thumbnail,   vf);
+    REGISTER_FILTER (TINTERLACE,  tinterlace,  vf);
     REGISTER_FILTER (TRANSPOSE,   transpose,   vf);
     REGISTER_FILTER (UNSHARP,     unsharp,     vf);
     REGISTER_FILTER (VFLIP,       vflip,       vf);
index fb2ce2b..0daa84b 100644 (file)
@@ -30,8 +30,8 @@
 #include "libavcodec/avcodec.h"
 
 #define LIBAVFILTER_VERSION_MAJOR  2
-#define LIBAVFILTER_VERSION_MINOR 55
-#define LIBAVFILTER_VERSION_MICRO 101
+#define LIBAVFILTER_VERSION_MINOR 56
+#define LIBAVFILTER_VERSION_MICRO 100
 
 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
                                                LIBAVFILTER_VERSION_MINOR, \
diff --git a/libavfilter/vf_tinterlace.c b/libavfilter/vf_tinterlace.c
new file mode 100644 (file)
index 0000000..7ba164e
--- /dev/null
@@ -0,0 +1,325 @@
+/*
+ * Copyright (c) 2011 Stefano Sabatini
+ * Copyright (c) 2010 Baptiste Coudurier
+ * Copyright (c) 2003 Michael Zucchi <notzed@ximian.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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU 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
+ * temporal field interlace filter, ported from MPlayer/libmpcodecs
+ */
+
+#include "libavutil/imgutils.h"
+#include "avfilter.h"
+#include "internal.h"
+
+typedef struct {
+    int mode;                   ///< interlace mode selected
+    int frame;                  ///< number of the output frame
+    int vsub;                   ///< chroma vertical subsampling
+    AVFilterBufferRef *cur;
+    AVFilterBufferRef *next;
+    uint8_t *black_data[4];     ///< buffer used to fill padded lines
+    int black_linesize[4];
+} TInterlaceContext;
+
+#define FULL_SCALE_YUVJ_FORMATS \
+    PIX_FMT_YUVJ420P, PIX_FMT_YUVJ422P, PIX_FMT_YUVJ444P, PIX_FMT_YUVJ440P
+
+static enum PixelFormat full_scale_yuvj_pix_fmts[] = {
+    FULL_SCALE_YUVJ_FORMATS, PIX_FMT_NONE
+};
+
+static int query_formats(AVFilterContext *ctx)
+{
+    static const enum PixelFormat pix_fmts[] = {
+        PIX_FMT_YUV420P,  PIX_FMT_YUV422P,  PIX_FMT_YUV444P,
+        PIX_FMT_YUV444P,  PIX_FMT_YUV410P,  PIX_FMT_YUVA420P,
+        PIX_FMT_GRAY8, FULL_SCALE_YUVJ_FORMATS,
+        PIX_FMT_NONE
+    };
+
+    avfilter_set_common_pixel_formats(ctx, avfilter_make_format_list(pix_fmts));
+    return 0;
+}
+
+static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque)
+{
+    TInterlaceContext *tinterlace = ctx->priv;
+    int n;
+    tinterlace->mode = 0;
+
+    if (args) {
+        n = sscanf(args, "%d", &tinterlace->mode);
+
+        if (n != 1 || tinterlace->mode < 0 || tinterlace->mode > 4) {
+            av_log(ctx, AV_LOG_ERROR,
+                   "Invalid mode '%s', use an integer between 0 and 4\n", args);
+            return AVERROR(EINVAL);
+        }
+    }
+
+    return 0;
+}
+
+static av_cold void uninit(AVFilterContext *ctx)
+{
+    TInterlaceContext *tinterlace = ctx->priv;
+
+    if (tinterlace->cur ) avfilter_unref_buffer(tinterlace->cur );
+    if (tinterlace->next) avfilter_unref_buffer(tinterlace->next);
+
+    av_freep(&tinterlace->black_data[0]);
+}
+
+static int config_out_props(AVFilterLink *outlink)
+{
+    AVFilterContext *ctx = outlink->src;
+    AVFilterLink *inlink = outlink->src->inputs[0];
+    const AVPixFmtDescriptor *desc = &av_pix_fmt_descriptors[outlink->format];
+    TInterlaceContext *tinterlace = ctx->priv;
+
+    tinterlace->vsub = desc->log2_chroma_h;
+    outlink->w = inlink->w;
+    outlink->h = tinterlace->mode == 0 || tinterlace->mode == 3 ?
+        inlink->h*2 : inlink->h;
+
+    if (tinterlace->mode == 3) {
+        uint8_t black[4] = { 16, 128, 128, 16 };
+        int i, ret;
+        if (ff_fmt_is_in(outlink->format, full_scale_yuvj_pix_fmts))
+            black[0] = black[3] = 0;
+        ret = av_image_alloc(tinterlace->black_data, tinterlace->black_linesize,
+                             outlink->w, outlink->h, outlink->format, 1);
+        if (ret < 0)
+            return ret;
+
+        /* fill black picture with black */
+        for (i = 0; i < 4 && tinterlace->black_data[i]; i++) {
+            int h = i == 1 || i == 2 ? outlink->h >> desc->log2_chroma_h : outlink->h;
+            memset(tinterlace->black_data[i], black[i],
+                   tinterlace->black_linesize[i] * h);
+        }
+    }
+    av_log(ctx, AV_LOG_INFO, "mode:%d h:%d -> h:%d\n",
+           tinterlace->mode, inlink->h, outlink->h);
+
+    return 0;
+}
+
+#define FIELD_UPPER           0
+#define FIELD_LOWER           1
+#define FIELD_UPPER_AND_LOWER 2
+
+/**
+ * Copy picture field from src to dst.
+ *
+ * @param src_field copy from upper, lower field or both
+ * @param interleave leave a padding line between each copied field
+ * @param dst_field copy to upper or lower field,
+ *        only meaningful when interleave is selected
+ */
+static inline
+void copy_picture_field(uint8_t *dst[4], int dst_linesize[4],
+                        uint8_t *src[4], int src_linesize[4],
+                        enum PixelFormat format, int w, int src_h,
+                        int src_field, int interleave, int dst_field)
+{
+    const AVPixFmtDescriptor *desc = &av_pix_fmt_descriptors[format];
+    int plane, vsub = desc->log2_chroma_h;
+    int k = src_field == FIELD_UPPER_AND_LOWER ? 1 : 2;
+
+    for (plane = 0; plane < desc->nb_components; plane++) {
+        int lines = plane == 1 || plane == 2 ? src_h >> vsub : src_h;
+        int linesize = av_image_get_linesize(format, w, plane);
+        uint8_t *dstp = dst[plane];
+        uint8_t *srcp = src[plane];
+        lines /= k;
+        if (src_field == FIELD_LOWER)
+            srcp += src_linesize[plane];
+        if (interleave && dst_field == FIELD_LOWER)
+            dstp += dst_linesize[plane];
+        av_image_copy_plane(dstp, dst_linesize[plane] * (interleave ? 2 : 1),
+                            srcp, src_linesize[plane]*k, linesize, lines);
+    }
+}
+
+static void start_frame(AVFilterLink *inlink, AVFilterBufferRef *picref)
+{
+    AVFilterContext *ctx = inlink->dst;
+    TInterlaceContext *tinterlace = ctx->priv;
+
+    if (tinterlace->cur)
+        avfilter_unref_buffer(tinterlace->cur);
+    tinterlace->cur  = tinterlace->next;
+    tinterlace->next = picref;
+}
+
+static void end_frame(AVFilterLink *inlink)
+{
+    AVFilterContext *ctx = inlink->dst;
+    AVFilterLink *outlink = ctx->outputs[0];
+    TInterlaceContext *tinterlace = ctx->priv;
+    AVFilterBufferRef *cur  = tinterlace->cur;
+    AVFilterBufferRef *next = tinterlace->next;
+    AVFilterBufferRef *out  = NULL;
+    int field;
+
+    /* we need at least two frames */
+    if (!tinterlace->cur)
+        return;
+
+    switch (tinterlace->mode) {
+    case 0: /* move the odd frame into the upper field of the new image, even into
+             * the lower field, generating a double-height video at half framerate */
+        out = avfilter_get_video_buffer(outlink, AV_PERM_WRITE, outlink->w, outlink->h);
+        avfilter_copy_buffer_ref_props(out, cur);
+        out->video->h = outlink->h;
+        out->video->interlaced = 1;
+        out->video->top_field_first = 1;
+
+        /* write odd frame lines into the upper field of the new frame */
+        copy_picture_field(out->data, out->linesize,
+                           cur->data, cur->linesize,
+                           inlink->format, inlink->w, inlink->h,
+                           FIELD_UPPER_AND_LOWER, 1, FIELD_UPPER);
+        /* write even frame lines into the lower field of the new frame */
+        copy_picture_field(out->data, out->linesize,
+                           next->data, next->linesize,
+                           inlink->format, inlink->w, inlink->h,
+                           FIELD_UPPER_AND_LOWER, 1, FIELD_LOWER);
+        avfilter_unref_buffer(tinterlace->next);
+        tinterlace->next = NULL;
+        break;
+
+    case 1: /* only output even frames, odd  frames are dropped; height unchanged, half framerate */
+    case 2: /* only output odd  frames, even frames are dropped; height unchanged, half framerate */
+        out = avfilter_ref_buffer(tinterlace->mode == 2 ? cur : next, AV_PERM_READ);
+        avfilter_unref_buffer(tinterlace->next);
+        tinterlace->next = NULL;
+        break;
+
+    case 3: /* expand each frame to double height, but pad alternate
+             * lines with black; framerate unchanged */
+        out = avfilter_get_video_buffer(outlink, AV_PERM_WRITE, outlink->w, outlink->h);
+        avfilter_copy_buffer_ref_props(out, cur);
+        out->video->h = outlink->h;
+
+        field = (1 + tinterlace->frame) & 1 ? FIELD_UPPER : FIELD_LOWER;
+        /* copy upper and lower fields */
+        copy_picture_field(out->data, out->linesize,
+                           cur->data, cur->linesize,
+                           inlink->format, inlink->w, inlink->h,
+                           FIELD_UPPER_AND_LOWER, 1, field);
+        /* pad with black the other field */
+        copy_picture_field(out->data, out->linesize,
+                           tinterlace->black_data, tinterlace->black_linesize,
+                           inlink->format, inlink->w, inlink->h,
+                           FIELD_UPPER_AND_LOWER, 1, !field);
+        break;
+
+    case 4: /* interleave upper lines from odd frames with lower lines from even frames,
+             * halving the frame rate and preserving image height */
+        out = avfilter_get_video_buffer(outlink, AV_PERM_WRITE, outlink->w, outlink->h);
+        avfilter_copy_buffer_ref_props(out, cur);
+        out->video->interlaced = 1;
+        out->video->top_field_first = 1;
+
+        /* copy upper field from cur */
+        copy_picture_field(out->data, out->linesize,
+                           cur->data, cur->linesize,
+                           inlink->format, inlink->w, inlink->h,
+                           FIELD_UPPER, 1, FIELD_UPPER);
+        /* copy lower fields from next */
+        copy_picture_field(out->data, out->linesize,
+                           next->data, next->linesize,
+                           inlink->format, inlink->w, inlink->h,
+                           FIELD_LOWER, 1, FIELD_LOWER);
+        avfilter_unref_buffer(tinterlace->next);
+        tinterlace->next = NULL;
+        break;
+    }
+
+    avfilter_start_frame(outlink, out);
+    avfilter_draw_slice(outlink, 0, outlink->h, 1);
+    avfilter_end_frame(outlink);
+
+    tinterlace->frame++;
+}
+
+static int poll_frame(AVFilterLink *outlink)
+{
+    TInterlaceContext *tinterlace = outlink->src->priv;
+    AVFilterLink *inlink = outlink->src->inputs[0];
+    int ret, val;
+
+    val = avfilter_poll_frame(inlink);
+
+    if (val == 1 && !tinterlace->next) {
+        if ((ret = avfilter_request_frame(inlink)) < 0)
+            return ret;
+        val = avfilter_poll_frame(inlink);
+    }
+    assert(tinterlace->next);
+
+    return val;
+}
+
+static int request_frame(AVFilterLink *outlink)
+{
+    TInterlaceContext *tinterlace = outlink->src->priv;
+    AVFilterLink *inlink = outlink->src->inputs[0];
+
+    do {
+        int ret;
+
+        if ((ret = avfilter_request_frame(inlink)) < 0)
+            return ret;
+    } while (!tinterlace->cur);
+
+    return 0;
+}
+
+static void null_draw_slice(AVFilterLink *link, int y, int h, int slice_dir) { }
+
+AVFilter avfilter_vf_tinterlace = {
+    .name          = "tinterlace",
+    .description   = NULL_IF_CONFIG_SMALL("Perform temporal field interlacing."),
+    .priv_size     = sizeof(TInterlaceContext),
+    .init          = init,
+    .uninit        = uninit,
+    .query_formats = query_formats,
+
+    .inputs = (const AVFilterPad[]) {
+        { .name          = "default",
+          .type          = AVMEDIA_TYPE_VIDEO,
+          .start_frame   = start_frame,
+          .draw_slice    = null_draw_slice,
+          .end_frame     = end_frame, },
+        { .name = NULL}
+    },
+    .outputs = (const AVFilterPad[]) {
+        { .name          = "default",
+          .type          = AVMEDIA_TYPE_VIDEO,
+          .config_props  = config_out_props,
+          .poll_frame    = poll_frame,
+          .request_frame = request_frame },
+        { .name = NULL}
+    },
+};