1/*
2 * Copyright (c) 2011 Mark Himsley
3 *
4 * This file is part of Libav.
5 *
6 * Libav is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * Libav is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with Libav; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21/**
22 * @file
23 * video field order filter, heavily influenced by vf_pad.c
24 */
25
26/* #define DEBUG */
27
28#include "libavutil/imgutils.h"
29#include "libavutil/pixdesc.h"
30#include "avfilter.h"
31
32typedef struct
33{
34    unsigned int dst_tff;      ///< output bff/tff
35    int          line_size[4]; ///< bytes of pixel data per line for each plane
36} FieldOrderContext;
37
38static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque)
39{
40    FieldOrderContext *fieldorder = ctx->priv;
41
42    const char *tff = "tff";
43    const char *bff = "bff";
44
45    if (!args) {
46        fieldorder->dst_tff = 1;
47    } else if (sscanf(args, "%u", &fieldorder->dst_tff) == 1) {
48        fieldorder->dst_tff = !!fieldorder->dst_tff;
49    } else if (!strcmp(tff, args)) {
50        fieldorder->dst_tff = 1;
51    } else if (!strcmp(bff, args)) {
52        fieldorder->dst_tff = 0;
53    } else {
54        av_log(ctx, AV_LOG_ERROR, "Invalid argument '%s'.\n", args);
55        return AVERROR(EINVAL);
56    }
57
58    av_log(ctx, AV_LOG_INFO, "output field order: %s\n",
59            fieldorder->dst_tff ? tff : bff);
60
61    return 0;
62}
63
64static int query_formats(AVFilterContext *ctx)
65{
66    AVFilterFormats  *formats;
67    enum PixelFormat pix_fmt;
68    int              ret;
69
70    /** accept any input pixel format that is not hardware accelerated, not
71     *  a bitstream format, and does not have vertically sub-sampled chroma */
72    if (ctx->inputs[0]) {
73        formats = NULL;
74        for (pix_fmt = 0; pix_fmt < PIX_FMT_NB; pix_fmt++)
75            if (!(  av_pix_fmt_descriptors[pix_fmt].flags & PIX_FMT_HWACCEL
76                 || av_pix_fmt_descriptors[pix_fmt].flags & PIX_FMT_BITSTREAM)
77                && av_pix_fmt_descriptors[pix_fmt].nb_components
78                && !av_pix_fmt_descriptors[pix_fmt].log2_chroma_h
79                && (ret = avfilter_add_format(&formats, pix_fmt)) < 0) {
80                avfilter_formats_unref(&formats);
81                return ret;
82            }
83        avfilter_formats_ref(formats, &ctx->inputs[0]->out_formats);
84        avfilter_formats_ref(formats, &ctx->outputs[0]->in_formats);
85    }
86
87    return 0;
88}
89
90static int config_input(AVFilterLink *inlink)
91{
92    AVFilterContext   *ctx        = inlink->dst;
93    FieldOrderContext *fieldorder = ctx->priv;
94    int               plane;
95
96    /** full an array with the number of bytes that the video
97     *  data occupies per line for each plane of the input video */
98    for (plane = 0; plane < 4; plane++) {
99        fieldorder->line_size[plane] = av_image_get_linesize(
100                inlink->format,
101                inlink->w,
102                plane);
103    }
104
105    return 0;
106}
107
108static AVFilterBufferRef *get_video_buffer(AVFilterLink *inlink, int perms, int w, int h)
109{
110    AVFilterContext   *ctx        = inlink->dst;
111    AVFilterLink      *outlink    = ctx->outputs[0];
112
113    return avfilter_get_video_buffer(outlink, perms, w, h);
114}
115
116static void start_frame(AVFilterLink *inlink, AVFilterBufferRef *inpicref)
117{
118    AVFilterContext   *ctx        = inlink->dst;
119    AVFilterLink      *outlink    = ctx->outputs[0];
120
121    AVFilterBufferRef *outpicref;
122
123    outpicref = avfilter_ref_buffer(inpicref, ~0);
124    outlink->out_buf = outpicref;
125
126    avfilter_start_frame(outlink, outpicref);
127}
128
129static void draw_slice(AVFilterLink *inlink, int y, int h, int slice_dir)
130{
131    AVFilterContext   *ctx        = inlink->dst;
132    FieldOrderContext *fieldorder = ctx->priv;
133    AVFilterLink      *outlink    = ctx->outputs[0];
134
135    AVFilterBufferRef *inpicref   = inlink->cur_buf;
136
137    /** can only currently do slices if this filter is doing nothing
138     *  because this filter is moving picture content, the output
139     *  slice will contain different video lines than the input slice
140     *  and that complexity will be added later */
141    if (  !inpicref->video->interlaced
142        || inpicref->video->top_field_first == fieldorder->dst_tff) {
143        avfilter_draw_slice(outlink, y, h, slice_dir);
144    }
145}
146
147static void end_frame(AVFilterLink *inlink)
148{
149    AVFilterContext   *ctx        = inlink->dst;
150    FieldOrderContext *fieldorder = ctx->priv;
151    AVFilterLink      *outlink    = ctx->outputs[0];
152
153    AVFilterBufferRef *inpicref   = inlink->cur_buf;
154    AVFilterBufferRef *outpicref  = outlink->out_buf;
155
156    int               h, plane, line_step, line_size, line;
157    uint8_t           *cpy_src, *cpy_dst;
158
159    if (    inpicref->video->interlaced
160         && inpicref->video->top_field_first != fieldorder->dst_tff) {
161        av_dlog(ctx,
162                "picture will move %s one line\n",
163                fieldorder->dst_tff ? "up" : "down");
164        h = inpicref->video->h;
165        for (plane = 0; plane < 4 && inpicref->data[plane]; plane++) {
166            line_step = inpicref->linesize[plane];
167            line_size = fieldorder->line_size[plane];
168            cpy_src = inpicref->data[plane];
169            cpy_dst = outpicref->data[plane];
170            if (fieldorder->dst_tff) {
171                /** Move every line up one line, working from
172                 *  the top to the bottom of the frame.
173                 *  The original top line is lost.
174                 *  The new last line is created as a copy of the
175                 *  penultimate line from that field. */
176                for (line = 0; line < h; line++) {
177                    if (1 + line < outpicref->video->h) {
178                        memcpy(cpy_dst, cpy_src + line_step, line_size);
179                    } else {
180                        memcpy(cpy_dst, cpy_src - line_step - line_step, line_size);
181                    }
182                    cpy_src += line_step;
183                    cpy_dst += line_step;
184                }
185            } else {
186                /** Move every line down one line, working from
187                 *  the bottom to the top of the frame.
188                 *  The original bottom line is lost.
189                 *  The new first line is created as a copy of the
190                 *  second line from that field. */
191                cpy_src += (h - 1) * line_step;
192                cpy_dst += (h - 1) * line_step;
193                for (line = h - 1; line >= 0 ; line--) {
194                    if (line > 0) {
195                        memcpy(cpy_dst, cpy_src - line_step, line_size);
196                    } else {
197                        memcpy(cpy_dst, cpy_src + line_step + line_step, line_size);
198                    }
199                    cpy_src -= line_step;
200                    cpy_dst -= line_step;
201                }
202            }
203        }
204        outpicref->video->top_field_first = fieldorder->dst_tff;
205        avfilter_draw_slice(outlink, 0, h, 1);
206    } else {
207        av_dlog(ctx,
208                "not interlaced or field order already correct\n");
209    }
210
211    avfilter_end_frame(outlink);
212    avfilter_unref_buffer(inpicref);
213}
214
215AVFilter avfilter_vf_fieldorder = {
216    .name          = "fieldorder",
217    .description   = NULL_IF_CONFIG_SMALL("Set the field order."),
218    .init          = init,
219    .priv_size     = sizeof(FieldOrderContext),
220    .query_formats = query_formats,
221    .inputs        = (AVFilterPad[]) {{ .name             = "default",
222                                        .type             = AVMEDIA_TYPE_VIDEO,
223                                        .config_props     = config_input,
224                                        .start_frame      = start_frame,
225                                        .get_video_buffer = get_video_buffer,
226                                        .draw_slice       = draw_slice,
227                                        .end_frame        = end_frame,
228                                        .min_perms        = AV_PERM_READ,
229                                        .rej_perms        = AV_PERM_REUSE2|AV_PERM_PRESERVE,},
230                                      { .name = NULL}},
231    .outputs       = (AVFilterPad[]) {{ .name             = "default",
232                                        .type             = AVMEDIA_TYPE_VIDEO, },
233                                      { .name = NULL}},
234};
235