[FFmpeg-devel] [PATCH] add fieldorder video filter

Stefano Sabatini stefano.sabatini-lala at poste.it
Thu Mar 31 16:15:22 CEST 2011


On date Thursday 2011-03-31 08:47:38 +0100, Mark Himsley encoded:
> Second version of this filter. Renamed and updated from the
> suggestions given for the first version.
> 
> 
> Converting to and from interlaced PAL DV files, with their
> bottom-field-first interlace field order, can be a pain. Converting
> tff files to DV results in tff DV files, which are hard to work with
> in editing software.
> 
> The attached filter can:
> 
> Convert field order by either moving all of the lines in the picture
> up by 1 line (bff to tff conversion) or down by 1 line (tff to bff
> conversion). The remaining line, the bottom line in bff to tff
> transforms or the top line in tff to bff transforms, is filled by
> copying the closest line in that field.
> 
> Previous to this filter I have used a filter chain like this to do
> bff to tff conversion.
> 
> format=yuv422p,crop=720:575:0:1,pad=720:576:0:0:black
> 
> but that chain does not fill the remaining line.
> 
> -- 
> Mark

> diff --git a/doc/filters.texi b/doc/filters.texi
> index 5193b66..b24b53f 100644
> --- a/doc/filters.texi
> +++ b/doc/filters.texi
> @@ -500,6 +500,35 @@ fade=in:0:25, fade=out:975:25
>  fade=in:5:20
>  @end example
>  
> + at section fieldorder
> +
> +Transform the field order of the input video.
> +
> +It requires one parameter: @var{tff}
> +
> + at var{tff} specifies the required field order that the input interlaced
> +video will be transformed to, and it accepts one of the following values:
> +
> + at table @option
> + at item 0
> +output bottom field first
> + at item 1
> +output top field first
> + at end table
> +
> +There is no default.
> +
> +Transformation is done by shifting the picture content up or down
> +by one line, and filling the remaining line with appropriate picture content.
> +This method is consistent with most broadcast field order converters.
> +
> +This filter is very useful when converting to or from PAL DV material,
> +which is bottom field first.
> +
> + at example
> +./ffmpeg -i in.vob -vf "fieldorder=0" out.dv
> + at end example
> +
>  @section fifo
>  
>  Buffer input images and send them when they are requested.
> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
> index 028aa52..8290b10 100644
> --- a/libavfilter/Makefile
> +++ b/libavfilter/Makefile
> @@ -29,6 +29,7 @@ OBJS-$(CONFIG_CROPDETECT_FILTER)             += vf_cropdetect.o
>  OBJS-$(CONFIG_DRAWBOX_FILTER)                += vf_drawbox.o
>  OBJS-$(CONFIG_DRAWTEXT_FILTER)               += vf_drawtext.o
>  OBJS-$(CONFIG_FADE_FILTER)                   += vf_fade.o
> +OBJS-$(CONFIG_FIELDORDER_FILTER)             += vf_fieldorder.o
>  OBJS-$(CONFIG_FIFO_FILTER)                   += vf_fifo.o
>  OBJS-$(CONFIG_FORMAT_FILTER)                 += vf_format.o
>  OBJS-$(CONFIG_FREI0R_FILTER)                 += vf_frei0r.o
> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
> index eb4cb9f..0668efd 100644
> --- a/libavfilter/allfilters.c
> +++ b/libavfilter/allfilters.c
> @@ -47,6 +47,7 @@ void avfilter_register_all(void)
>      REGISTER_FILTER (DRAWBOX,     drawbox,     vf);
>      REGISTER_FILTER (DRAWTEXT,    drawtext,    vf);
>      REGISTER_FILTER (FADE,        fade,        vf);
> +    REGISTER_FILTER (FIELDORDER,  fieldorder,  vf);
>      REGISTER_FILTER (FIFO,        fifo,        vf);
>      REGISTER_FILTER (FORMAT,      format,      vf);
>      REGISTER_FILTER (FREI0R,      frei0r,      vf);
> diff --git a/libavfilter/vf_fieldorder.c b/libavfilter/vf_fieldorder.c
> new file mode 100644
> index 0000000..aca0eda
> --- /dev/null
> +++ b/libavfilter/vf_fieldorder.c
> @@ -0,0 +1,203 @@
> +/*
> + * video field order filter
> + * copyright (c) 2011 Mark Himsley
> + * Heavily influenced by vf_pad.c
> + *
> + * 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
> + */
> +
> +/* #define DEBUG */
> +
> +#include "libavutil/pixdesc.h"
> +#include "avfilter.h"
> +
> +typedef struct
> +{
> +    unsigned int dst_tff;      ///< output bff/tff
> +    int          line_step[4]; ///< bytes per pixel per each plane
> +    int          hsub;         ///< chroma subsampling value
> +} FieldOrderContext;
> +
> +static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque)
> +{
> +    FieldOrderContext *fieldorder = ctx->priv;
> +
> +    if (!args || !sscanf(args, "%u", &fieldorder->dst_tff) == 1) {
> +        av_log(ctx, AV_LOG_ERROR,
> +                "Expected 1 argument '#':'%s'\n", args);
> +        return AVERROR(EINVAL);
> +    }
> +    fieldorder->dst_tff = !!fieldorder->dst_tff;
> +
> +    av_log(ctx, AV_LOG_INFO, "ttf:%d\n", fieldorder->dst_tff);
> +
> +    return 0;
> +}
> +
> +static int query_formats(AVFilterContext *ctx)
> +{
> +    AVFilterFormats *formats;
> +    enum PixelFormat pix_fmt;
> +    int ret;
> +
> +    /** accept any input pixel format that is not hardware accelerated
> +     *  and does not have vertically sub-sampled chroma */
> +    if (ctx->inputs[0]) {
> +        formats = NULL;
> +        for (pix_fmt = 0; pix_fmt < PIX_FMT_NB; pix_fmt++)
> +            if (   !(av_pix_fmt_descriptors[pix_fmt].flags & PIX_FMT_HWACCEL)
> +                && av_pix_fmt_descriptors[pix_fmt].nb_components
> +                && !av_pix_fmt_descriptors[pix_fmt].log2_chroma_h
> +                && (ret = avfilter_add_format(&formats, pix_fmt)) < 0) {
> +                avfilter_formats_unref(&formats);
> +                return ret;
> +            }
> +        avfilter_formats_ref(formats, &ctx->inputs[0]->out_formats);
> +        avfilter_formats_ref(formats, &ctx->outputs[0]->in_formats);
> +    }
> +
> +    return 0;
> +}
> +

> +static int config_input(AVFilterLink *inlink)
> +{
> +    AVFilterContext   *ctx        = inlink->dst;
> +    FieldOrderContext *fieldorder = ctx->priv;
> +
> +    int is_packed_rgba;
> +    int plane, component;
> +
> +    /** discover if the pixel format is packed or planar */
> +    is_packed_rgba = 1;

> +    for (component = 0; component < av_pix_fmt_descriptors[inlink->format].nb_components; component++) {
> +        if (av_pix_fmt_descriptors[inlink->format].comp[component].plane != 0) {
> +            is_packed_rgba = 0;
> +        }
> +    }

Nit, technically this is not necessarily packed RGBA, for example
we support some YUV packed formats, e.g. uyvy and uyyvyy.

> +
> +    /** discover the bytes per pixel for each plane */
> +    if (is_packed_rgba) {
> +        fieldorder->line_step[0] = av_get_bits_per_pixel(&av_pix_fmt_descriptors[inlink->format])>>3;
> +    } else {
> +        for (plane = 0; plane < 4; plane++) {
> +            fieldorder->line_step[plane] = 1 + (av_pix_fmt_descriptors[inlink->format].comp->depth_minus1 >> 3 );
> +        }
> +    }

Check av_image_fill_max_pixsteps() in libavutil/imgutils.h, I suspect
that you can use it in place of the above code.

> +    fieldorder->hsub = av_pix_fmt_descriptors[inlink->format].log2_chroma_w;
> +
> +    return 0;
> +}
> +

> +static void draw_slice(AVFilterLink *link, int y, int h, int slice_dir)
> +{
> +    /** cannot do slices because the filter has
> +     *  to read from pixels outside of the slice */
> +}

Nit++: call it null_draw_slice() { }, this is the convention used in
other similar filters.

> +static void end_frame(AVFilterLink *inlink)
> +{
> +    AVFilterContext   *ctx        = inlink->dst;
> +    FieldOrderContext *fieldorder = ctx->priv;
> +    AVFilterLink      *outlink    = ctx->outputs[0];
> +
> +    AVFilterBufferRef *inpic      = inlink->cur_buf;
> +    AVFilterBufferRef *outpic     = outlink->out_buf;
> +
> +    int plane, line, linesize, linewidth, hsub, h, w;
> +    uint8_t *cpy_src, *cpy_dst;
> +
> +    h = outpic->video->h;
> +    w = outpic->video->w;
> +
> +    if (inpic->video->interlaced) {
> +        if (inpic->video->top_field_first != fieldorder->dst_tff) {
> +            av_dlog(ctx,
> +                    "picture will move %s one line\n",
> +                    fieldorder->dst_tff ? "up" : "down");
> +            for (plane = 0; plane < 4 && outpic->data[plane]; plane++) {
> +                linesize = outpic->linesize[plane];
> +                hsub = plane == 1 || plane == 2 ? fieldorder->hsub : 0;

> +                linewidth = (w >> hsub) * fieldorder->line_step[plane];

This unfortunately can't work with "weird" packed formats, like UYVY,
UYYVYY etc (packed formats with subsampled components).

You can add a condition for skipping them in query_formats, or simply
enumerate the working formats, whatever you prefer.

> +                cpy_src = inpic->data[plane];
> +                cpy_dst = outpic->data[plane];
> +                if (fieldorder->dst_tff) {
> +                    /** Move every line up one line, working from
> +                     *  the top to the bottom of the frame.
> +                     *  The original top line is lost.
> +                     *  The new last line is created as a copy of the
> +                     *  penultimate line from that field. */
> +                    for (line = 0; line < h; line++) {
> +                        if (1 + line < outpic->video->h) {
> +                            memcpy(cpy_dst, cpy_src + linesize, linewidth);
> +                        } else {
> +                            memcpy(cpy_dst, cpy_src - linesize - linesize, linewidth);
> +                        }
> +                        cpy_src += linesize;
> +                        cpy_dst += linesize;
> +                    }
> +                } else {
> +                    /** Move every line down one line, working from
> +                     *  the bottom to the top of the frame.
> +                     *  The original bottom line is lost.
> +                     *  The new first line is created as a copy of the
> +                     *  second line from that field. */
> +                    cpy_src += (h - 1) * linesize;
> +                    cpy_dst += (h - 1) * linesize;
> +                    for (line = h - 1; line >= 0 ; line--) {
> +                        if ( line > 0) {
> +                            memcpy(cpy_dst, cpy_src - linesize, linewidth);
> +                        } else {
> +                            memcpy(cpy_dst, cpy_src + linesize + linesize, linewidth);
> +                        }
> +                        cpy_src -= linesize;
> +                        cpy_dst -= linesize;
> +                    }
> +                }
> +            }
> +            outpic->video->top_field_first = fieldorder->dst_tff;

> +        } else {
> +            av_dlog(ctx,
> +                    "field order already correct\n");
> +        }
> +    }

Missing copy.

BTW, if !inpic->video->interlaced you can avoid to copy and use the
usual draw_slice() mechanism by adopting the technique used here:
http://thread.gmane.org/gmane.comp.video.ffmpeg.devel/119581/focus=121439

but I'm OK with simply copying the frame here if you believe it looks
too complicate.
-- 
FFmpeg = Fierce Fancy Miracolous Pitiless Elitarian Game


More information about the ffmpeg-devel mailing list