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

Stefano Sabatini stefano.sabatini-lala at poste.it
Fri Apr 8 17:36:12 CEST 2011


On date Friday 2011-04-08 12:10:32 +0100, Mark Himsley encoded:
> On 31/03/11 08:47, Mark Himsley wrote:
> >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.
> 
> New version attached.
> 
> removes bitstream formats from the accepted list of formats
> hopefully addresses Stefano's usability nit's
> makes tff the default
> implements Stefano's suggestion for accepting string values in input
> implements using av_image_get_linesize() to find the number of bytes
> of video in a line
> updated docs
> 
> -- 
> Mark

> diff --git a/doc/filters.texi b/doc/filters.texi
> index 5193b66..accc2b1 100644
> --- a/doc/filters.texi
> +++ b/doc/filters.texi
> @@ -500,6 +500,39 @@ 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 accepts 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 or bff
> +output bottom field first
> + at item 1 or tff
> +output top field first
> + at end table
> +

> +Dedault value is "tff".

Default

> +
> +Transformation is achieved 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.
> +
> +If the input video is not flagged as being interlaced, or it is already
> +flagged as being of the required output field order then this filter does
> +not alter the incoming video.
> +
> +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

Nit:
For example:
@example
...
@end example

Nit++: maybe fieldorder=bff is more explicative.

> +
>  @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..a54b94b
> --- /dev/null
> +++ b/libavfilter/vf_fieldorder.c
> @@ -0,0 +1,231 @@
> +/*
> + * 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"
> +#include "libavutil/imgutils.h"

Nit+++:

#include "libavutil/imgutils.h"
#include "libavutil/pixdesc.h"
#include "avfilter.h"

> +
> +typedef struct
> +{
> +    unsigned int dst_tff;      ///< output bff/tff
> +    int          line_size[4]; ///< bytes of pixel data per line for each plane
> +    int          line_size_set;
> +} FieldOrderContext;
> +
> +static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque)
> +{
> +    FieldOrderContext *fieldorder = ctx->priv;
> +

> +    const char        *tff = "tff";
> +    const char        *bff = "bff";

Here you can skip a confusing level of indirection, by directly using
"tff" and "bff" below.

> +
> +    if (!args) {
> +        fieldorder->dst_tff = 1;
> +    } else if (sscanf(args, "%u", &fieldorder->dst_tff) == 1) {
> +        fieldorder->dst_tff = !!fieldorder->dst_tff;

> +    } else if (!strcmp(tff,args)) {

Nit+++: tff,args -> tff,_args

> +        fieldorder->dst_tff = 1;
> +    } else if (!strcmp(bff,args)) {
> +        fieldorder->dst_tff = 0;
> +    } else {
> +        av_log(ctx, AV_LOG_ERROR,
> +                "Expected one argument, none found in '%s'\n",
> +                args == NULL ? "" : args);
> +        return AVERROR(EINVAL);
> +    }
> +
> +    av_log(ctx, AV_LOG_INFO, "output field order: %s\n",
> +            fieldorder->dst_tff ? tff : bff);
> +
> +    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, not
> +     *  a bitstream format, 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].flags & PIX_FMT_BITSTREAM)
> +                && 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 AVFilterBufferRef *get_video_buffer(AVFilterLink *inlink, int perms, int w, int h)
> +{
> +    AVFilterContext   *ctx        = inlink->dst;
> +    AVFilterLink      *outlink    = ctx->outputs[0];
> +
> +    return avfilter_get_video_buffer(outlink, perms, w, h);
> +}
> +
> +static void start_frame(AVFilterLink *inlink, AVFilterBufferRef *inpicref)
> +{
> +    AVFilterContext   *ctx        = inlink->dst;
> +    FieldOrderContext *fieldorder = ctx->priv;
> +    AVFilterLink      *outlink    = ctx->outputs[0];
> +
> +    int               plane;
> +    AVFilterBufferRef *outpicref;
> +
> +    /** only one time, full an array with the number of bytes that the video
> +     *  data occupies per line for each plane of the input video */
> +    if(!fieldorder->line_size_set) {

Nit: if_(...)


> +        for (plane = 0; plane < 4; plane++) {
> +            fieldorder->line_size[plane] = av_image_get_linesize(
> +                    inlink->format,
> +                    inpicref->video->w,
> +                    plane);
> +        }
> +        fieldorder->line_size_set = 1;
> +    }

And I'd prefer to put this in config_props, this way you don't need
line_size_set and the code is slightly more readable.

> +
> +    outpicref = avfilter_ref_buffer(inpicref, ~0);
> +    outlink->out_buf = outpicref;
> +
> +    avfilter_start_frame(outlink, outpicref);
> +}
> +
> +static void draw_slice(AVFilterLink *inlink, int y, int h, int slice_dir)
> +{
> +    AVFilterContext   *ctx        = inlink->dst;
> +    FieldOrderContext *fieldorder = ctx->priv;
> +    AVFilterLink      *outlink    = ctx->outputs[0];
> +
> +    AVFilterBufferRef *inpicref   = inlink->cur_buf;
> +
> +    /** can only currently do slices if this filter is doing nothing
> +     *  because this filter is moving picture content, the output
> +     *  slice will contain different video lines than the input slice
> +     *  and that complexity will be added later */
> +    if (  !inpicref->video->interlaced
> +        || inpicref->video->top_field_first == fieldorder->dst_tff) {
> +        avfilter_draw_slice(outlink, y, h, slice_dir);
> +    }
> +}
> +
> +static void end_frame(AVFilterLink *inlink)
> +{
> +    AVFilterContext   *ctx        = inlink->dst;
> +    FieldOrderContext *fieldorder = ctx->priv;
> +    AVFilterLink      *outlink    = ctx->outputs[0];
> +
> +    AVFilterBufferRef *inpicref   = inlink->cur_buf;
> +    AVFilterBufferRef *outpicref  = outlink->out_buf;
> +
> +    int               h, w, plane, line_step, line_size, line;
> +    uint8_t           *cpy_src, *cpy_dst;
> +
> +    if (    inpicref->video->interlaced
> +         && inpicref->video->top_field_first != fieldorder->dst_tff) {
> +        av_dlog(ctx,
> +                "picture will move %s one line\n",
> +                fieldorder->dst_tff ? "up" : "down");
> +        h = inpicref->video->h;
> +        w = inpicref->video->w;
> +        for (plane = 0; plane < 4 && inpicref->data[plane]; plane++) {
> +            line_step = inpicref->linesize[plane];
> +            line_size = fieldorder->line_size[plane];
> +            cpy_src = inpicref->data[plane];
> +            cpy_dst = outpicref->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 < outpicref->video->h) {
> +                        memcpy(cpy_dst, cpy_src + line_step, line_size);
> +                    } else {
> +                        memcpy(cpy_dst, cpy_src - line_step - line_step, line_size);
> +                    }
> +                    cpy_src += line_step;
> +                    cpy_dst += line_step;
> +                }
> +            } 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) * line_step;
> +                cpy_dst += (h - 1) * line_step;
> +                for (line = h - 1; line >= 0 ; line--) {

> +                    if ( line > 0) {
                         ^
Nit+++: if (line > 0)

[...]

As for the bitstream support, basically they are streams for which
each pixel component can't be expressed as a finite number of bytes
but only as a finite number of bits.

For example for rgb4_byte you have:
RGGB____

For monowhite:
BWBWBWBW

Note that av_image_get_linesize() returns a number of pixels also for
bitstream formats, so the logic should be unchanged.

If you're interested you may implement a regression test for the
filter, this is a little tricky as it needs some knowledge of the
internal test system (you basically needs to provide the checksum of
the output issued by the test, the better way is to see how it is done
for another filter, check for example the patch for the copy or the
pixdesctest filter), but again no need to delay the application of
this.
-- 
FFmpeg = Fierce Furious Martial Pitiless Ephemeral Gadget


More information about the ffmpeg-devel mailing list