[FFmpeg-devel] [PATCH 2/2] lavfi: add tile video filter. (WIP)

Nicolas George nicolas.george at normalesup.org
Thu Feb 23 14:44:27 CET 2012


Signed-off-by: Nicolas George <nicolas.george at normalesup.org>
---
 doc/filters.texi         |   10 ++
 libavfilter/Makefile     |    1 +
 libavfilter/allfilters.c |    1 +
 libavfilter/vf_tile.c    |  225 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 237 insertions(+), 0 deletions(-)
 create mode 100644 libavfilter/vf_tile.c


There are still two problems with that filter:

- When using ffmpeg -i file -vf tile ... the last incomplete frame is not
  flushed. Apparently, ffmpeg does not try to poll the filtergraph after it
  has fed it the last decoded frame.

- When using -f lavfi, the last incomplete frame is flushed, but the empty
  frames, instead of being blank, keep the content they had on the last
  frame. This one is easy to fix, but it requires some extension/cleanup of
  the drawutils API, and it is sorely lacking documentation.

Apart from that, it does work.

Regards,

-- 
  Nicolas George


diff --git a/doc/filters.texi b/doc/filters.texi
index 238401a..9487a31 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -2597,6 +2597,16 @@ 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
 
+ at section tile
+
+Tile several successive frames together.
+
+It accepts as argument the tile size in the form "@var{w}:@var{h}".
+
+ at example
+ffmpeg -skip_frame nokey -i file.avi -vf 'scale=128:72,tile=8:8' -an -vsync 0 keyframes%03d.png
+ at end example
+
 @section tinterlace
 
 Perform various types of temporal field interlacing.
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 9f5fdcb..e263778 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -87,6 +87,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_TILE_FILTER)                   += vf_tile.o
 OBJS-$(CONFIG_TINTERLACE_FILTER)             += vf_tinterlace.o
 OBJS-$(CONFIG_TRANSPOSE_FILTER)              += vf_transpose.o
 OBJS-$(CONFIG_UNSHARP_FILTER)                += vf_unsharp.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 487738a..8d6efdd 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -95,6 +95,7 @@ void avfilter_register_all(void)
     REGISTER_FILTER (SLICIFY,     slicify,     vf);
     REGISTER_FILTER (SPLIT,       split,       vf);
     REGISTER_FILTER (THUMBNAIL,   thumbnail,   vf);
+    REGISTER_FILTER (TILE,        tile,        vf);
     REGISTER_FILTER (TINTERLACE,  tinterlace,  vf);
     REGISTER_FILTER (TRANSPOSE,   transpose,   vf);
     REGISTER_FILTER (UNSHARP,     unsharp,     vf);
diff --git a/libavfilter/vf_tile.c b/libavfilter/vf_tile.c
new file mode 100644
index 0000000..ad57688
--- /dev/null
+++ b/libavfilter/vf_tile.c
@@ -0,0 +1,225 @@
+/*
+ * Copyright (c) 2012 Nicolas George
+ *
+ * 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
+ * tile video filter
+ */
+
+#include "libavutil/pixdesc.h"
+#include "avfilter.h"
+#include "drawutils.h"
+
+typedef struct {
+    unsigned w, h;
+    unsigned current;
+    int pixel_step[4];
+    uint8_t hsub, vsub;
+} Tile_context;
+
+#define REASONABLE_SIZE 1024
+
+static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque)
+{
+    Tile_context *tile = ctx->priv;
+    int r;
+    char dummy;
+
+    if (!args)
+        return AVERROR(EINVAL);
+    r = sscanf(args, "%u:%u%c", &tile->w, &tile->h, &dummy);
+    if (r != 2 || !tile->w || !tile->h)
+        return AVERROR(EINVAL);
+    if (tile->w > REASONABLE_SIZE || tile->h > REASONABLE_SIZE) {
+        av_log(ctx, AV_LOG_ERROR, "Tile size %ux%u is insane.\n",
+               tile->w, tile->h);
+        return AVERROR(EINVAL);
+    }
+    return 0;
+}
+
+static int query_formats(AVFilterContext *ctx)
+{
+    avfilter_set_common_pixel_formats(ctx,
+                                      ff_drawutils_supported_pixel_formats());
+    return 0;
+}
+
+static int config_props(AVFilterLink *outlink)
+{
+    AVFilterContext *ctx = outlink->src;
+    Tile_context *tile   = ctx->priv;
+    AVFilterLink *inlink = ctx->inputs[0];
+    const AVPixFmtDescriptor *pixdesc = &av_pix_fmt_descriptors[inlink->format];
+    uint8_t *line[4] = { 0 }, color[4] = { 0 };
+    int is_packed_rgba, i;
+
+    if (inlink->w > INT_MAX / tile->w) {
+        av_log(ctx, AV_LOG_ERROR, "Total width %ux%u is too much.\n",
+               tile->w, inlink->w);
+        return AVERROR(EINVAL);
+    }
+    if (inlink->h > INT_MAX / tile->h) {
+        av_log(ctx, AV_LOG_ERROR, "Total height %ux%u is too much.\n",
+               tile->h, inlink->h);
+        return AVERROR(EINVAL);
+    }
+    outlink->w = tile->w * inlink->w;
+    outlink->h = tile->h * inlink->h;
+    outlink->sample_aspect_ratio = inlink->sample_aspect_ratio;
+    /* drawutil stuff */
+    tile->hsub = pixdesc->log2_chroma_w;
+    tile->vsub = pixdesc->log2_chroma_h;
+    ff_fill_line_with_color(line, tile->pixel_step, 0, color,
+                            inlink->format, color, &is_packed_rgba, NULL);
+    for (i = 0; i < FF_ARRAY_ELEMS(line); i++)
+        av_free(line[i]);
+    return 0;
+}
+
+/* Note: direct rendering is not possible since there is no guarantee that
+ * buffers are fed to start_frame in the order they were obtained from
+ * get_buffer (think B-frames). */
+
+static void start_frame(AVFilterLink *inlink, AVFilterBufferRef *picref)
+{
+    AVFilterContext *ctx  = inlink->dst;
+    Tile_context *tile    = ctx->priv;
+    AVFilterLink *outlink = ctx->outputs[0];
+
+    if (tile->current)
+        return;
+    outlink->out_buf = avfilter_get_video_buffer(outlink, AV_PERM_WRITE,
+                                                 outlink->w, outlink->h);
+    avfilter_copy_buffer_ref_props(outlink->out_buf, picref);
+    outlink->out_buf->video->w = outlink->w;
+    outlink->out_buf->video->h = outlink->h;
+    avfilter_start_frame(outlink, outlink->out_buf);
+}
+
+static void draw_slice(AVFilterLink *inlink, int y, int h, int slice_dir)
+{
+    AVFilterContext *ctx  = inlink->dst;
+    Tile_context *tile    = ctx->priv;
+    AVFilterLink *outlink = ctx->outputs[0];
+    unsigned x0 = inlink->w * (tile->current % tile->w);
+    unsigned y0 = inlink->h * (tile->current / tile->w);
+
+    ff_copy_rectangle(outlink->out_buf->data, outlink->out_buf->linesize,
+                      inlink ->cur_buf->data, inlink ->cur_buf->linesize,
+                      tile->pixel_step, tile->hsub, tile->vsub,
+                      x0, y0 + y, y, inlink->cur_buf->video->w, h);
+    /* TODO if tile->w == 1 && slice_dir is always 1, we could draw_slice
+     * immediately. */
+}
+
+static void draw_blank_frame(AVFilterContext *ctx)
+{
+    Tile_context *tile    = ctx->priv;
+    /* TODO */
+    tile->current++;
+}
+static void end_last_frame(AVFilterContext *ctx)
+{
+    Tile_context *tile    = ctx->priv;
+    AVFilterLink *outlink = ctx->outputs[0];
+
+    while (tile->current < tile->w * tile->h)
+        draw_blank_frame(ctx);
+    avfilter_draw_slice(outlink, 0, outlink->out_buf->video->h, 1);
+    avfilter_end_frame(outlink);
+    tile->current = 0;
+}
+
+static void end_frame(AVFilterLink *inlink)
+{
+    AVFilterContext *ctx  = inlink->dst;
+    Tile_context *tile    = ctx->priv;
+
+    avfilter_unref_buffer(inlink->cur_buf);
+    if (++tile->current == tile->w * tile->h)
+        end_last_frame(ctx);
+}
+
+static int poll_frame(AVFilterLink *outlink)
+{
+    AVFilterContext *ctx = outlink->src;
+    Tile_context *tile   = ctx->priv;
+    AVFilterLink *inlink = ctx->inputs[0];
+    int r;
+
+    r = avfilter_poll_frame(inlink);
+    if (r <= 0)
+        return r;
+    r = (r + tile->current) / (tile->w * tile->h);
+    if (r)
+        return r;
+    /* no frame currently available, let us pump the input */
+    r = avfilter_request_frame(inlink);
+    return r < 0 && tile->current > 0; /* last partial frame */
+}
+
+static int request_frame(AVFilterLink *outlink)
+{
+    AVFilterContext *ctx = outlink->src;
+    Tile_context *tile   = ctx->priv;
+    AVFilterLink *inlink = ctx->inputs[0];
+    int r;
+
+    while (1) {
+        r = avfilter_request_frame(inlink);
+        if (r < 0) { /* EOF -> flush partial frame */
+            if (tile->current)
+                end_last_frame(ctx);
+            else
+                return r;
+            break;
+        }
+        if (!tile->current) /* done */
+            break;
+    }
+    return 0;
+}
+
+
+AVFilter avfilter_vf_tile = {
+    .name          = "tile",
+    .description   = NULL_IF_CONFIG_SMALL("Tile several successive frames together."),
+    .init          = init,
+    .query_formats = query_formats,
+    .priv_size     = sizeof(Tile_context),
+    .inputs = (const AVFilterPad[]) {
+        { .name        = "default",
+          .type        = AVMEDIA_TYPE_VIDEO,
+          .start_frame = start_frame,
+          .draw_slice  = draw_slice,
+          .end_frame   = end_frame,
+          .min_perms   = AV_PERM_READ, },
+        { .name = NULL }
+    },
+    .outputs = (const AVFilterPad[]) {
+        { .name          = "default",
+          .type          = AVMEDIA_TYPE_VIDEO,
+          .config_props  = config_props,
+          .poll_frame    = poll_frame,
+          .request_frame = request_frame },
+        { .name = NULL }
+    },
+};
-- 
1.7.9



More information about the ffmpeg-devel mailing list